Automapper seems to be ignoring grandchild objects when mapping between objects. So that i only get 2 levels of recursion. I need at least 3, ideally n levels.
How do I get Automapper to return the correct object with all children intact?
I can work around it, by serializing my DbPOCO and deserializing to the Dto, but that means crossing concerns in my application, and/or doing something different for this specific method which I don't like.
Given my DB Poco
public class Menu : SqlTable, IParentChild<Menu>
{
public string Label {get; set;}
public string Route {get; set;}
public float Sequence { get; set; }
public int? ParentID {get; set;}
public Menu Parent {get; set;}
public IList<Menu> Children { get; set; }
}
I can return (with some recursive magic) an object which serializes like this, but it includes (removed) database audit columns and other table generic items that I don't want communicated over my API.
[
{
"Label": "Main Menu 1",
"Route": "",
"Sequence": 1,
"ParentID": null,
"Parent": null,
"Children": [
{
"Label": "Menu 1 Sub 1",
"Route": "",
"Sequence": 1,
"ParentID": 1,
"Parent": null,
"Children": [
{
"Label": "Menu 1 Sub 1 Sub 1",
"Route": "",
"Sequence": 1,
"ParentID": 2,
"Parent": null,
"Children": [],
...
},
{
"Label": "Menu 1 Sub 1 Sub 2",
"Route": "",
"Sequence": 2,
"ParentID": 2,
"Parent": null,
"Children": [],
...
}
],
...
},
{
"Label": "Menu 1 Sub 2",
"Route": "",
"Sequence": 2,
"ParentID": 1,
"Parent": null,
"Children": [],
"ID": 5,
...
}
],
...
},
{
"Label": "Main Menu 2",
"Route": "",
"Sequence": 2,
"ParentID": null,
"Parent": null,
"Children": [
{
"Label": "Menu 2 Sub 1",
"Route": "",
"Sequence": 1,
"ParentID": 6,
"Parent": null,
"Children": [],
...
}
],
...
}
]
When i pass this into Automapper, to map it to the following DTO
public class WebApplicationMenuModel
{
public string Label { get; set; }
public string Route { get; set; }
public float Sequence { get; set; }
public IEnumerable<WebApplicationMenuModel> Children { get; set; }
}
The grandchildren from my original object are set to null
[
{
"Label": "Main Menu 1",
"Route": "",
"Sequence": 1,
"Children": [
{
"Label": "Menu 1 Sub 1",
"Route": "",
"Sequence": 1,
"Children": null
},
{
"Label": "Menu 1 Sub 2",
"Route": "",
"Sequence": 2,
"Children": null
}
]
},
{
"Label": "Main Menu 2",
"Route": "",
"Sequence": 2,
"Children": [
{
"Label": "Menu 2 Sub 1",
"Route": "",
"Sequence": 1,
"Children": null
}
]
}
]
The map profile is fairly basic
public class MenuProfile : Profile
{
public MenuProfile()
{
CreateMap<Menu, WebApplicationMenuModel>()
.ReverseMap();
}
}
See Reply by Lucian Bargaoanu. Needed to user the master Automapper branch (9.1.0-ci-01627) to support this from MyGet.
https://docs.automapper.org/en/stable/The-MyGet-build.html
Related
Is it possible to override completely the #context of an api-platform ld+json response ?
(I had to replace the localhost urls with <base-api-url>, otherwise the question was marked as spam)
I have this PHP Dto :
#[ApiResource(
shortName: 'Account',
types: [
'https://www.w3.org/ns/activitystreams',
],
operations: [
new Get(
uriTemplate: '/accounts/{id}',
uriVariables: [
'id' => new Link(
fromProperty: 'id',
fromClass: Account::class,
)
],
controller: GetPersonController::class,
normalizationContext: [
'groups' => ['activitypub'],
'jsonld_embed_context' => true
],
provider: AccountStateProvider::class
)
]
)]
class Person
{
#[ApiProperty(identifier: true)]
#[Groups(['activitypub'])]
public string $id;
#[Groups(['activitypub'])]
public string $type = "Person";
#[Groups(['activitypub'])]
public string $following;
[...]
And the response is :
{
"#context": {
"#vocab": "<base-api-url>docs.jsonld#",
"hydra": "http://www.w3.org/ns/hydra/core#",
"id": "Account/id",
"type": "Account/type",
"following": "Account/following",
"followers": "Account/followers",
"inbox": "Account/inbox",
"outbox": "Account/outbox",
"name": "Account/name",
"preferredUsername": "Account/preferredUsername",
"summary": "Account/summary",
"url": "Account/url",
"published": "Account/published"
},
"#id": "/accounts/0185f9ad-5602-7aef-bac3-1a4d1b32a409",
"#type": "https://www.w3.org/ns/activitystreams",
"id": "<base-api-url>accounts/0185f9ad-5602-7aef-bac3-1a4d1b32a409",
"type": "Person",
"following": "<base-api-url>accounts/0185f9ad-5602-7aef-bac3-1a4d1b32a409/followings",
"followers": "<base-api-url>accounts/0185f9ad-5602-7aef-bac3-1a4d1b32a409/followers",
"inbox": "<base-api-url>accounts/0185f9ad-5602-7aef-bac3-1a4d1b32a409/inbox",
"outbox": "<base-api-url>accounts/0185f9ad-5602-7aef-bac3-1a4d1b32a409/outbox",
"name": "Test User",
"preferredUsername": "testuser",
"summary": "",
"url": "https://localhost/#testuser",
"published": "2023-01-28T18:39:24+00:00"
}
What I need is :
{
"#context": [
"https://www.w3.org/ns/activitystreams"
],
"id": "<base-api-url>accounts/0185f9ad-5602-7aef-bac3-1a4d1b32a409",
"type": "Person",
"following": "<base-api-url>accounts/0185f9ad-5602-7aef-bac3-1a4d1b32a409/followings",
"followers": "<base-api-url>accounts/0185f9ad-5602-7aef-bac3-1a4d1b32a409/followers",
"inbox": "<base-api-url>accounts/0185f9ad-5602-7aef-bac3-1a4d1b32a409/inbox",
"outbox": "<base-api-url>accounts/0185f9ad-5602-7aef-bac3-1a4d1b32a409/outbox",
"name": "Test User",
"preferredUsername": "testuser",
"summary": "",
"url": "https://localhost/#testuser",
"published": "2023-01-28T18:39:24+00:00"
}
We are running a Dotnet Core 2.2 service using ServiceStack 5.7, and need to throttle it. So we want to put it behind a Azure Api Management Gateway (apim) - it runs in a Azure App Service.
We have enabled OpenApi feature using
self.Plugins.Add(new OpenApiFeature());
When we export our OpenApi definition we get the following:
"paths": {
...
"/api/search": {
"post": {
"tags": [
"api"
],
"operationId": "SearchRequestsearch_Post",
"consumes": [
"application/x-www-form-urlencoded"
],
"produces": [
"application/json"
],
"parameters": [
{
"name": "Filters",
"in": "formData",
"type": "array",
"items": {
"$ref": "#/definitions/FilterDto"
},
"collectionFormat": "multi",
"required": false
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/SearchResponse"
}
}
},
"deprecated": false,
"security": [
{
"Bearer": []
}
]
},
"parameters": [
{
"$ref": "#/parameters/Accept"
}
]
}
}
...
"definitions": {
"FilterDto": {
"title": "FilterDto",
"properties": {
"Field": {
"description": "The field to filter on",
"type": "string",
"enum": [
"None",
"DestinationName",
"DocumentId"
]
},
"Values": {
type": "array",
"items": {
"type": "string"
}
},
"Type": {
"type": "string",
"enum": [
"Equals",
"NotEquals",
"RangeNumeric",
"RangeDate"
]
}
},
"description": "FilterDto",
"type": "object"
}
...
}
The problem is that it is not supported to have a parameter with an array of a type (defined in #/definitions/FilterDto). And it fails with:
Parsing error(s): JSON is valid against no schemas from 'oneOf'. Path 'paths['/api/search'].post.parameters[1]', line 1, position 666.
Parsing error(s): The input OpenAPI file is not valid for the OpenAPI specificate https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md (schema https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v2.0/schema.json).
In the Azure portal.
In c# (ServiceStack) we have defined the following:
public class SearchRequest : SearchRequestBase, IReturn<SearchResponse>
{
public SearchRequest()
{
Filters = new List<FilterDto>();
}
[ApiMember(Name = "Filters"]
public List<FilterDto> Filters { get; set; }
}
public class FilterDto
{
[ApiMember(Name = "Field"]
[ApiAllowableValues("Field", typeof(FilterAndFacetField))]
public FilterAndFacetField Field { get; set; }
[ApiMember(Name = "Values")]
public List<string> Values { get; set; }
[ApiMember(Name = "Type")]
[ApiAllowableValues("Type", typeof(FilterType))]
public FilterType Type { get; set; }
public FilterDto()
{
Values = new List<string>();
}
}
Have anyone successfully managed to import a OpenApi using array of $ref in the parameters from ServiceStack into a Api Management?
I'm unable to return the child data from the second table linked buy a Foreign key with the first table.
I have the following Tables that has been created using Entity Framework Core Code first approach :
public class BoardGame
{
public int id { get; set; }
public double price { get; set; }
public List<BoardGameTitle> BoardGameTitle{get;set;}
}
public class BoardGameTitle
{
public int id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int languageId { get; set; }
public int BoardGameId{get;set;}
public BoardGame BoardGame{get;set;}
}
public class TableFlipDB :DbContext{
public TableFlipDB (DbContextOptions<TableFlipDB> options)
:base(options)
{}
public DbSet<BoardGame> BoardGame{get;set;}
public DbSet<BoardGameTitle> BoardGameTitle{get;set;}
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.Entity<BoardGame>().HasMany(bg=>bg.BoardGameTitle).WithOne(bg=>bg.BoardGame);
//modelBuilder.Entity<BoardGameTitle>().HasOne(BoardGameTitle=>BoardGameTitle.BoardGame).WithMany(BoardGameTitle=>BoardGameTitle.BoardGameTitle);
}
}
the second table BoardGameTitle contains a Foreign key to the following table BoardGame through the property BoardGameId,
When ever I retrieve the list of board games I cant get the related data from the second table in the property BoardGameTitle the property returns null
my Controller
public ActionResult<List<BoardGame>> GetAll()
{
return TableFlipDB.BoardGame.ToList();
}
Expected Result was:
[
{
"id": 1,
"price": 100,
"boardGameTitle": [
{
"id": 1,
"name": "b1",
"description": "b1",
"languageId": 1,
"boardGameId": 1,
"boardGame": {
"id": 1,
"price": 100,
"boardGameTitle": []
}
}
]
},
{
"id": 2,
"price": 200,
"boardGameTitle": [
{
"id": 2,
"name": "b2",
"description": "b2",
"languageId": 1,
"boardGameId": 2,
"boardGame": {
"id": 2,
"price": 200,
"boardGameTitle": [
{
"id": 3,
"name": "b22",
"description": "b22",
"languageId": 2,
"boardGameId": 2
}
]
}
},
{
"id": 3,
"name": "b22",
"description": "b22",
"languageId": 2,
"boardGameId": 2,
"boardGame": {
"id": 2,
"price": 200,
"boardGameTitle": [
{
"id": 2,
"name": "b2",
"description": "b2",
"languageId": 1,
"boardGameId": 2
}
]
}
}
]
}
]
But the Result I got
[
{
"id": 1,
"price": 100,
"boardGameTitle": null
},
{
"id": 2,
"price": 200,
"boardGameTitle": null
}
]
I have found the solution as #Elyas Esna in the comments section said I need to change the controller return statement from return TableFlipDB.BoardGame.ToList(); to return TableFlipDB.BoardGame.Include(p=>p.BoardGameTitle).ToList();
I just start to use Api platform and immediately stuck with problem how to filter data.
I have entity User and i want to filter data that are present in response ( JSON API format)
{
"links": {
"self": "/api/users"
},
"meta": {
"totalItems": 2,
"itemsPerPage": 30,
"currentPage": 1
},
"data": [
{
"id": "/api/users/1",
"type": "User",
"attributes": {
"_id": 1,
"username": "jonhdoe",
"isActive": true,
"address": null
}
},
{
"id": "/api/users/3",
"type": "User",
"attributes": {
"_id": 3,
"username": "test",
"isActive": true,
"address": null
}
}
]
}
so I want to remove e.g. User with id 3, but not use filters sent via request. I just want to set filter that will be always run when someone go to /api/users.
I look to api-platform extensions but this will be applied on each request e.g. /api/trucks. So at end I just want to get something like
{
"links": {
"self": "/api/users"
},
"meta": {
"totalItems": 1,
"itemsPerPage": 30,
"currentPage": 1
},
"data": [
{
"id": "/api/users/1",
"type": "User",
"attributes": {
"_id": 1,
"username": "jonhdoe",
"isActive": true,
"address": null
}
}
]
}
As you pointed out, extensions are the way to go.
The applyToCollection method gets a $resourceClass parameter containing the current resource class.
So you can apply the WHERE clause only for a specific class in this method like:
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
{
if (User::class === $resourceClass) {
$queryBuilder->doSomething();
}
// Do nothing for other classes
}
I have a JSON something like this:
{
"key": "Target",
"value": {
"__type": "Entity:http://schemas.microsoft.com/xrm/2011/Contracts",
"Attributes": [
{
"key": "prioritycode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "completeinternalreview",
"value": false
},
{
"key": "stepname",
"value": "10-Lead"
},
{
"key": "createdby",
"value": {
"__type": "EntityReference:http://schemas.microsoft.com/xrm/2011/Contracts",
"Id": "ca2ead0c-8786-e511-80f9-3863bb347b18",
"KeyAttributes": [],
"LogicalName": "systemuser",
"Name": null,
"RowVersion": null
}
}
]
}
}
How do the toke for the key/values by searching for the value of the key?
Eg I want to get the key value pair 'completeinternalreview'
Assuming you have a C# class like this to represent that attributes object from your JSON:
public class MyValue
{
[JsonProperty("Attributes")]
public List<KeyValuePair<string, object>> Attributes { get; set; }
}
you can simply deserialize the string:
var result = JsonConvert.DeserializeObject<KeyValuePair<string, MyValue>>(jsonString);
and then find the correct key-value pair with:
var kvp = result.Value.Attributes.Find(a => a.Value == "completeinternalreview");