Extra items in JSON data when using Web API - aspnetboilerplate

I have a problem with the JSON data from the Web API service.
In a regular Web API Controller, I get the result given below.
[
{
"title": "başlık",
"description": "Tanımlama",
"creationTime": "2018-01-15T17:20:06.9801797",
"state": 0,
"assignedPersonId": "afd46520-521d-4945-a4ee-083893e1d14c",
"assignedPersonName": "derya",
"id": 2
},
{
"title": "title",
"description": "description",
"creationTime": "2018-01-15T17:17:26.5161288",
"state": 0,
"assignedPersonId": null,
"assignedPersonName": null,
"id": 1
}
]
But when using ASP.NET Boilerplate infrastructure, I get the same data as:
{
"result": {
"items": [
{
"title": "başlık",
"description": "Tanımlama",
"creationTime": "2018-01-15T17:20:06.9801797",
"state": 0,
"assignedPersonId": "afd46520-521d-4945-a4ee-083893e1d14c",
"assignedPersonName": "derya",
"id": 2
},
{
"title": "title",
"description": "description",
"creationTime": "2018-01-15T17:17:26.5161288",
"state": 0,
"assignedPersonId": null,
"assignedPersonName": null,
"id": 1
}
]
},
"targetUrl": null,
"success": true,
"error": null,
"unAuthorizedRequest": false,
"__abp": true
}
It seems that the actual raw data is nested in an outer data structure. Because of this, deserialization like below doesn't work.
List<Class1> data = JsonConvert.DeserializeObject<List<Class1>>(JSONString);
And I have to manage some string operations on JSONString.
Am I doing something wrong? Thanks in advance.

From the documentation on WrapResult and DontWrapResult Attributes:
You can control wrapping using WrapResult and DontWrapResult attributes for an action or all actions of a controller.
ASP.NET MVC Controllers
ASP.NET Boilerplate wraps ASP.NET MVC action results by default if return type is JsonResult (or Task<JsonResult> for async actions). You can change this by using WrapResult attribute as shown below:
public class PeopleController : AbpController
{
[HttpPost]
[WrapResult(WrapOnSuccess = false, WrapOnError = false)]
public JsonResult SavePerson(SavePersonModel person)
{
// TODO: save new person to database and return new person's id
return Json(new {PersonId = 42});
}
}
As a shortcut, we can just use [DontWrapResult] which is identical for this example.
You can change this default behaviour from startup configuration.
This applies not only to ASP.NET MVC Controllers, but also to ASP.NET Web API Controllers, Dynamic Web API Layer and ASP.NET Core Controllers.

Thanks, #Aaron for such a wonderful clean answer.
#Inanc, If you can map this JSON response with the below-mentioned class also.
JSON:
{
"result": {
"items": [
{
"title": "başlık",
"description": "Tanımlama",
"creationTime": "2018-01-15T17:20:06.9801797",
"state": 0,
"assignedPersonId": "afd46520-521d-4945-a4ee-083893e1d14c",
"assignedPersonName": "derya",
"id": 2
},
{
"title": "title",
"description": "description",
"creationTime": "2018-01-15T17:17:26.5161288",
"state": 0,
"assignedPersonId": null,
"assignedPersonName": null,
"id": 1
}
]
},
"targetUrl": null,
"success": true,
"error": null,
"unAuthorizedRequest": false,
"__abp": true
}
Code:
public class Item
{
public string title { get; set; }
public string description { get; set; }
public DateTime creationTime { get; set; }
public int state { get; set; }
public string assignedPersonId { get; set; }
public string assignedPersonName { get; set; }
public int id { get; set; }
}
public class Result
{
public List<Item> items { get; set; }
}
public class RootObject
{
public Result result { get; set; }
public object targetUrl { get; set; }
public bool success { get; set; }
public object error { get; set; }
public bool unAuthorizedRequest { get; set; }
public bool __abp { get; set; }
}
Deserialization Code:
List<RootObject> data = JsonConvert.DeserializeObject<List<RootObject>>(JSONString);

Add these two lines to the PreInitialize() method of your Web.Core project module (<ProjectName>WebCoreModuleModule.cs): docs
Configuration.Modules.AbpAspNetCore().DefaultWrapResultAttribute.WrapOnError = false;
Configuration.Modules.AbpAspNetCore().DefaultWrapResultAttribute.WrapOnSuccess = false;

Related

HotChocolate GraphQL filtering on GUID

I have very limited knowledge on GraphQL as I am still in the learning process. Now I stumbled upon an issue that I cannot resolve by myself without some help.
I'm using HotChocolate in my service.
I have a class ConsumerProductCategory with a Guid as Id which has a parent that is also a ConsumerProductCategory (think category > sub-category > ...)
Now I want to get the sub categories for a specific category, in linq you would write:
.Where(cat => cat.Parent.Id == id)
First of all lets start with our classes:
public class BaseViewModel : INode
{
public virtual Guid Id { get; set; }
}
public class ConsumerProductCategory : BaseViewModel
{
public ConsumerProductCategory()
{
}
public string Name { get; set; }
[UsePaging]
[UseFiltering]
[UseSorting]
public List<ConsumerProduct> Products { get; set; } = new List<ConsumerProduct>();
public ConsumerProductCategoryImage Image { get; set; }
public ConsumerProductCategory Parent { get; set; } = null;
public bool HasParent => this.Parent != null;
}
The object type definition is like this:
public class ConsumerProductCategoryType : ObjectType<ConsumerProductCategory>
{
protected override void Configure(IObjectTypeDescriptor<ConsumerProductCategory> descriptor)
{
descriptor
.Name(nameof(ConsumerProductCategory));
descriptor
.Description("Categories.");
descriptor
.Field(x => x.Id)
//.Type<UuidType>()
.Type<IdType>()
.Description($"{nameof(ConsumerProductCategory)} Id.");
descriptor
.Field(x => x.Name)
.Type<StringType>()
.Description($"{nameof(ConsumerProductCategory)} name.");
descriptor
.Field(x => x.Parent)
.Description($"{nameof(ConsumerProductCategory)} parent category.");
descriptor
.Field(x => x.Products)
.Description($"{nameof(ConsumerProductCategory)} products.");
descriptor
.ImplementsNode()
.IdField(t => t.Id)
.ResolveNode((context, id) => context.Service<IConsumerProductCategoryService>().GetByIdAsync(id));
}
}
The query to get the "main" categories would be like this:
query GetAllCategories {
consumerProductCategories(
#request: { searchTerm: "2"}
first: 10
after: null
where: { hasParent: { eq: false } }
order: {
name: ASC
}
) {
nodes {
id
name
image {
url
alt
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
This returns this result:
{
"data": {
"consumerProductCategories": {
"nodes": [
{
"id": "Q29uc3VtZXJQcm9kdWN0Q2F0ZWdvcnkKZ2EyOTYxNmRlMWMzMjQ4ZTU4YTU2YzRjYjdhMGQ5NmY5",
"name": "Category 1",
"image": {
"url": "https://picsum.photos/200",
"alt": "Category 1 Image"
}
},
{
"id": "Q29uc3VtZXJQcm9kdWN0Q2F0ZWdvcnkKZ2NmZWI0YzNiMGQyNjQyOWI4MGU0MmQ1NGNjYWE1N2Q4",
"name": "Category 2",
"image": {
"url": "https://picsum.photos/200",
"alt": "Category 2 Image"
}
},
{
"id": "Q29uc3VtZXJQcm9kdWN0Q2F0ZWdvcnkKZ2I0MjhjYWE2NGMxNTQ4MTdiMjM1ZWFhZWU3OGRhYWYz",
"name": "Category 3",
"image": {
"url": "https://picsum.photos/200",
"alt": "Category 3 Image"
}
}
],
"pageInfo": {
"endCursor": "Mg==",
"hasNextPage": false
}
}
}
}
The first thing I noticed was that the Id's (Guid's) are changed to some base64 encoded strings.
Weird, but if I would do this:
query {
node(
id: "Q29uc3VtZXJQcm9kdWN0Q2F0ZWdvcnkKZ2EyOTYxNmRlMWMzMjQ4ZTU4YTU2YzRjYjdhMGQ5NmY5"
) {
... on ConsumerProductCategory {
id
name
}
}
}
this perfectly works, result:
{
"data": {
"node": {
"id": "Q29uc3VtZXJQcm9kdWN0Q2F0ZWdvcnkKZ2EyOTYxNmRlMWMzMjQ4ZTU4YTU2YzRjYjdhMGQ5NmY5",
"name": "Category 1"
}
}
}
However, now I want to filter on the Parent.Id,
query GetSubcategories {
consumerProductCategories(
first: 10
after: null
where: { parent: { id: { eq: "Q29uc3VtZXJQcm9kdWN0Q2F0ZWdvcnkKZ2NmZWI0YzNiMGQyNjQyOWI4MGU0MmQ1NGNjYWE1N2Q4"}} }
order: {
name: ASC
}
) {
nodes {
id
name
image {
url
alt
}
parent {
id
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
This gives an error that the fieldtype where I do the "eq" is not correct, makes sense because in the data it's actually a Guid.
The result:
{
"errors": [
{
"message": "The specified value type of field `eq` does not match the field type.",
"locations": [
{
"line": 5,
"column": 31
}
],
"path": [
"consumerProductCategories"
],
"extensions": {
"fieldName": "eq",
"fieldType": "UUID",
"locationType": "UUID",
"specifiedBy": "http://spec.graphql.org/June2018/#sec-Values-of-Correct-Type"
}
}
]
}
I understand why it gives me this error, but I have no clue how to resolve this.
I looked everywhere on Google but have not found a similar question and in the official docs of HotChocolate I cannot really find a solution for this issue.
Can anyone point me in the right direction?
By the way, is it a good practice to use these "autogenerated" base64 strings as Id's, or is there some way to specify that this generation should not happen and actually return the Guid's instead?
Thanks in advance!
Ok, I can answer my own question, basically it isn't supported yet: github
What I've done for now is just add a second Guid in the base class:
This results in:

How do I filter a list of objects with linq with multiple criteria?

I am trying to filter a list of objects by multiple criteria. First of all, if the Name and Code are the same for two or more records, I only want to return one. However, if the Status of one is "suspended" and the Status of the second is "rented", I only want to return the record with "suspended" status. This is what I've got so far:
public class Customer
{
public string UnitName { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public string Status { get; set; }
}
public static IEnumerable<Customer> FilterDuplicates(IEnumerable<Customer> customers)
{
return customers
.GroupBy(c => new {c.Name, c.Code})
.Select(g => g.First());
}
For example, the following data should return Mister Twister, Frank Furter and the record where the status is "suspended" for Jane Jones:
{
"unitName": "A22",
"code": "00122",
"status": "rented",
"phone": "2125551212",
"name": "Jones, Jane"
},
{
"unitName": "A07",
"code": "00122",
"status": "suspended",
"phone": "2125551212",
"name": "Jones, Jane"
},
{
"unitName": "C19",
"code": "00222",
"status": "suspended",
"phone": "2125557777",
"name": "Furter, Frank"
},
{
"unitName": "B14",
"code": "00333",
"status": "rented",
"phone": "2125559999",
"name": "Twister, Mister"
}
You should order the contents of each group and then you can select which one you want:
return customers
.GroupBy(c => new {c.Name, c.Code})
.Select(g => g.OrderByDescending(c => c.status).First());
NOTE: This is essentially a particular implementation of DistinctBy, which in full general form I implement with:
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> src, Func<T, TKey> keySelector, Func<IGrouping<TKey, T>, T> pickFn, IEqualityComparer<TKey> comparer = null) =>
src.GroupBy(keySelector, comparer).Select(pickFn);
Which you could then use as
return customers.DistinctBy(c => new { c.Name, c.Code }),
g => g.OrderByDescending(c => c.status).First());

ElasticSearch and Nest: Why am I missing the id field on a query?

I created a simple object that represents a Meeting that has elements such as time, location, name, topic, etc and indexed it in ElasticSearch via Nest. It has an Id field that I leave blank so that ES can generate them.
Later on I retrieved all the documents that are missing GEO coordinates so that I may update them. All my returned elements still have null for the id field and when I update them back to ES it creates new documents for them.
What am I missing here that makes all my id's null?
Thank you
Here is the Meeting class (the id prop is redundant but I tried it anyway)
[ElasticType(IdProperty = "Id")]
public class Meeting
{
public string Id { get; set; }
public string Code { get; set; }
public string Day { get; set; }
public string Town { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public string OriginalTime { get; set; }
public string OriginalTimeCleaned { get; set; }
public string Handicap { get; set; }
public string FormattedAddress { get; set; }
public Coordinates Coordinates { get; set; }
public List<MeetingTime> Times = new List<MeetingTime>();
public bool IsProcessed { get; set; }
}
Here is how I retrieve meetings
public static List<Meeting> GetAddressesWithMissingCoordinates()
{
var result = Client.Search<Meeting>(s => s
.Index("meetings")
.AllTypes()
.Query(p => p.Filtered(f => f.Filter(x => x.Missing(c => c.Coordinates)))));
return result.Documents.ToList();
}
Here is my update statement, Id is null
public static void UpdateMeetingCoordinates(Meeting meeting, Coordinates coordinates)
{
meeting.Coordinates = coordinates;
var response = Client.Index(meeting, u => u
.Index("meetings")
.Type("meeting")
//.Id(meeting.Id.ToString())
.Refresh()
);
Console.WriteLine(response);
}
I've tried partial updates as well with no luck.
There is a way to get the internal id, as described in this issue requesting this very feature.
Rather than using response.Documents, do this instead:
var results = response.Hits.Select(hit =>
{
var result = hit.Source;
result.Id = hit.Id;
return result;
});
Elasticsearch sets an "_id" meta-data parameter (for which it chooses a value if you don't specify one), but it doesn't set that value in your document source.
To illustrate, if I create a trivial index:
PUT /test_index
then give it a couple of documents, without specifying "_id":
POST /test_index/doc/_bulk
{"index":{}}
{"id":null,"name":"doc1"}
{"index":{}}
{"id":null,"name":"doc2"}
and then search:
POST /test_index/_search
this is what I get back:
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 1,
"hits": [
{
"_index": "test_index",
"_type": "doc",
"_id": "AVEmuVlmj_RE0PsHCpza",
"_score": 1,
"_source": {
"id": null,
"name": "doc2"
}
},
{
"_index": "test_index",
"_type": "doc",
"_id": "AVEmuVlmj_RE0PsHCpzZ",
"_score": 1,
"_source": {
"id": null,
"name": "doc1"
}
}
]
}
}
Notice that the "_id" meta-data parameter was set for both documents, but the "id" field I passed is unchanged. That's because, as far as Elasticsearch is concerned, "id" is just another document field.
(here is the code I used: http://sense.qbox.io/gist/777dafae88311c4105453482050c64d69ccd09db)

Elasticsearch NEST - Filtering on multilevel nested types

I have a document model like this:
"customer": {
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"orders": {
"type": "nested",
"properties": {
"id": { "type": "integer" },
"orderDate" : { "type": "date", "format" : "YYYY-MM-dd" },
"orderLines": {
"type": "nested",
"properties": {
"seqno": { "type": "integer" },
"quantity": { "type": "integer" },
"articleId": { "type": "integer" }
}
}
}
}
}
}
A customer can have 0, 1 or multiple orders and an order can have 0, 1 or multiple orderLines
(this is a model I created for this question, as I think this is data everyone can understand, so if you spot any mistakes, please let me know, but don't let them distract you from my actual question)
I want to create a query with NEST which selects a (or all) customers with a specific value for customer.id, but only if they have at least one orderLine with a specific articleId.
I've looked at Need concrete documentation / examples of building complex index using NEST ElasticSearch library and Matching a complete complex nested collection item instead of separate members with Elastic Search, but was unable to create the query. Based upon the second question, I got to the point where I wrote
var results = client.Search<customer>(s => s
.From(0)
.Size(10)
.Types(typeof(customer))
.Query(q =>
q.Term(c => c.id, 12345)
&& q.Nested(n => n
.Path(c => c.order)
.Query(q2 => q2.Nested(n2 => n2
.Path(o => o.???))))
)
);
I expected the second Path to use order (orders is List) as generic type, but it is customer.
What is the code for the correct query?
In addition: is there more elaborate documentation of the search/query/filtering methods of NEST than the documentation on http://nest.azurewebsites.net/? In the first referenced question, both the links to the complex query tutorial (in question) and the unit test examples (accepted answer) do not work (timeout and 404 respectively).
Assuming we are modelling the customer to something on these lines
class customer
{
public int id { get; set; }
public string name { get; set;}
public class Orders {
public int id { get; set;}
public string orderData { get; set;}
public class OrderLines
{
public int seqno { get; set; }
public int quantity { get; set; }
public int articleId { get; set; }
}
[ElasticProperty(Type = FieldType.Nested)]
public List<OrderLines> orderLines { get; set; }
}
[ElasticProperty(Type = FieldType.Nested)]
public List<Orders> orders { get; set; }
};
The query in the above case would be :
var response = client.Search<customer>(
s => s.Index(<index_name_here>).Type("customer")
.Query(q => q.Term(p=>p.id, 1)
&&
q.Nested(n =>
n.Path("orders")
.Query(q2=> q2.Nested(
n2 => n2.Path("orders.orderLines")
.Query(q3 =>
q3.Term(c=>c.orders.First().orderLines.First().articleId, <article_id_here>)))))
));
As far as documentation the best I have come across is the same as the one you posted in the question and the resources linked there.

Asp.Net MVC 3 ModelBinding Arrays

I am posting something that looks like this:
FavoritePerson: "Dennis"
FavoriteAnimals: [{Type="Bear", Name="Bruno"}, {Type="Shark", Name="Sammy"}, ...]
Is there some shape for the Model to be that the DefaultModelBinder would be able to handle this? Something like
class FavoriteAnimalSubmission {
string Type {get; set;}
string Name {get; set;}
}
[HttpPost]
public MarkFavorites(string favoritePerson, FavoriteAnimalSubmission[] favoriteAnimals[]) {
...
}
Will fill favoritePerson and favoriteAnimals.Count but not the properties on each animal.
There is nothing out of the box that will handle a mixture of JSON (which in your case is invalid) and standard url encoded parameters. You will have to write a custom model binder if you ever needed to handle this request. Or simply modify your request to:
{
"FavoriteAnimals": [
{
"Type": "Bear",
"Name": "Bruno"
},
{
"Type": "Shark",
"Name": "Sammy"
}
],
"FavoritePerson": "Dennis"
}
and then on the server:
public class MyViewModel
{
public string FavoritePerson { get; set; }
public FavoriteAnimalSubmission[] FavoriteAnimals { get; set; }
}
public class FavoriteAnimalSubmission
{
public string Type { get; set; }
public string Name { get; set; }
}
and your controller action:
[HttpPost]
public MarkFavorites(MyViewModel model)
{
...
}
and the AJAX request to invoke it:
var model =
{
"FavoriteAnimals": [
{
"Type": "Bear",
"Name": "Bruno"
},
{
"Type": "Shark",
"Name": "Sammy"
}
],
"FavoritePerson": "Dennis"
};
$.ajax({
url: '#Url.Action("MarkFavorites", "SomeController")',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(model),
success: function(result) {
// do something with the result
}
});

Resources