How to return object references in ASP.net core Web API - rest

I dont understand why getAllAssemblies() return referenced objects where "getAssembly(int id)" does not.
Can you give a heads up on how to have referenced objects returned from both methods?
The type:
public class Assembly
{
public int Id { get; init; }
public string? Name { get; set; }
public List<Object>? ChildrenObjects { get; set; }
[Required]
public int ParrentObjectId { get; set; }
[JsonIgnore]
public Assembly? ParrentObject { get; set; }
}
The data:
modelBuilder.Entity<Assembly>().HasData(
new Assembly() { Id = 10, Name = "1" },
new Assembly() { Id = 11, Name = "1.1", ParrentObjectId = 10
});
GetAssembly(int id)
[HttpGet("{id}")]
public async Task<ActionResult> GetAssembly(int id)
{
return Ok(await _objectsContext.Assemblies.FindAsync(id));
}
returns:
{
"id": 10,
"name": "1",
"childrenObjects": null,
"parrentObjectId": 0
}
GetAllAssemblies()
public async Task<ActionResult> GetAllAssemblies()
{
return Ok(await _objectsContext.Assemblies.ToListAsync());
}
returns:
[
{
"id": 10,
"name": "1",
"childrenObjects": [
{
"id": 11,
"name": "1.1",
"childrenObjects": null,
"parrentObjectId": 10
}
],
"parrentObjectId": 0
},
{
"id": 11,
"name": "1.1",
"childrenObjects": null,
"parrentObjectId": 10
}
]

Related

Multiple Reference Navigation properties to same table

In a EF Core model there are two classes, Company and Employee. 1 Company can have multiple Employees. I am wondering if I can add to reference navigation properties (collection of employees with different roles, i.e. manager role) referencing the same employees table and have EF Core figure out which employees to display in what property, depending on a property of the employee class (isManager, true/false).
See below, would this be possible?
public class Company
{
public int Id { get; set; }
public string Name { get; set; } = string.empty;
public virtual ICollection<Employee> Minions { get; set; }
public virtual ICollection<Employee> Managers { get; set; }
public Company()
{
Minions = new HashSet<Employee>();
Managers = new HashSet<Employee>();
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.empty;
public int CompanyId { get; set; }
public virtual Company Company { get; set; } = null!;
public bool IsManager { get; set; }
}
Id
Name
1
"MonkeyBizz Inc."
Id
Name
CompanyId
IsManager
1
"John Doe"
1
0
2
"Jane Doe"
1
0
3
"Skippy the Magnificent"
1
1
{
"id": 1,
"name": "MonkeyBizz Inc.",
"minions": [
{ "id": 1, "name": "John Doe", "companyId": 1, "isManager": false },
{ "id": 2, "name": "Jane Doe", "companyId": 1, "isManager": false }
],
"managers": [
{ "id": 3, "name": "Skippy the Magnificent", "companyId": 1, "isManager": true }
]
}

Why my EF6 generated relationship not working?

I have two tables (Jobs and Versions) on a One to Many relationship in a PostgreSQL database. I have generated a DB Context and Models with dotnet-ef (EF6 Database-first) and created routes with JsonApiDotNetCore.
My two models :
// A Version (with one job)
[DisplayName("version")]
[Table("Versions")]
public partial class Version : Identifiable<long>
{
[Attr(PublicName = "id-version")]
public override long Id { get; set; }
[Attr(PublicName = "id-job")]
public long JobId { get; set; }
[Attr(PublicName = "name")]
public string Name { get; set; }
[Attr(PublicName = "job")]
public virtual Job JobIdNavigation { get; set; }
}
// A Job (with multiple Versions)
[DisplayName("job")]
[Table("Jobs")]
public partial class Job : Identifiable<long>
{
public Job()
{
this.Versions = new HashSet<Version>();
}
[Attr(PublicName = "id-job")]
public override long Id { get; set; }
[Attr(PublicName = "name")]
public string Name { get; set; }
[Attr(PublicName = "versions")]
public virtual ICollection<Version> Versions { get; set; }
}
And a DB Context :
public partial class TalendExplorerDbContext : DbContext
{
[...]
public virtual DbSet<Job> Jobs { get; set; }
public virtual DbSet<Version> Versions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasPostgresExtension("adminpack")
.HasPostgresExtension("ltree")
.HasAnnotation("Relational:Collation", "French_France.1252");
modelBuilder.Entity<Job>(entity =>
{
entity.HasKey(e => e.Id)
.HasName("Jobs_pkey");
entity.Property(e => e.Id)
.HasColumnName("job_id")
.UseIdentityAlwaysColumn();
[...]
});
modelBuilder.Entity<Version>(entity =>
{
entity.HasKey(e => e.Id)
.HasName("Versions_pkey");
entity.Property(e => e.Id)
.HasColumnName("version_id")
.UseIdentityAlwaysColumn();
entity.Property(e => e.JobId).HasColumnName("job_id");
[...]
entity.HasOne<Job>(d => d.JobIdNavigation)
.WithMany(p => p.Versions)
.HasForeignKey(d => d.JobId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("Versions_id_job_fkey");
});
OnModelCreatingPartial(modelBuilder);
}
[...]
}
With those, I can get a resource like a Job but there is no Versions linked:
{
"links": {
"self": "https://localhost:5001/jobs/2"
},
"data": {
"type": "jobs",
"id": "2",
"attributes": {
"name": "job2",
"versions": []
},
"links": {
"self": "https://localhost:5001/jobs/2"
}
}
}
Is this normal behavior ? How can I make my relationship work ?
Edit 1
As suggest by Michael Mairegger, I try to include the relationship on the request:
from https://localhost:5001/jobs/2
to https://localhost:5001/jobs/2?include=versions
but got an error:
{
"errors": [
{
"id": "4c6d79c9-0af7-419b-b89c-d3a61588b73a",
"status": "400",
"title": "The specified include is invalid.",
"detail": "Relationship 'versions' does not exist on resource 'jobs'.",
"source": { "parameter": "include" }
}
]
}
I am not familiar with JsonApiDotNetCore but I think the reason is the same as in ODATA. The API does not load related data because you did not request it. Otherwise it can happen that you accidently load the whole database because every data is somehow connected to any other data.
The API supports an include query parameter where you can request the additional navigation properties. I think if you execute https://localhost:5001/jobs/2?include=versions the versions shall be in the results set.
See: Including Relationships for further information.
As suggest by a co-worker (and Michael Mairegger), I had to specify relationship with JsonApiDotNetCore.
So I need to change annotations on models:
// A Version (with one job)
[DisplayName("version")]
[Table("Versions")]
public partial class Version : Identifiable<long>
{
[...]
[HasOne(PublicName = "job")]
public virtual Job JobIdNavigation { get; set; }
}
// A Job (with multiple Versions)
[DisplayName("job")]
[Table("Jobs")]
public partial class Job : Identifiable<long>
{
[...]
[HasMany(PublicName = "versions")]
public virtual ICollection<Version> Versions { get; set; }
}
Now, the request https://localhost:5001/jobs/2 show a version (relationship is working! hooray!):
{
"links": {
"self": "https://localhost:5001/jobs/2"
},
"data": {
"type": "jobs",
"id": "2",
"attributes": {
"inserted-date": "2021-07-07T00:00:00+02:00",
"modification-date": null,
"path": "test",
"purpose": "purpose",
"description": "job de test",
"name": "job1",
"description-capture": null,
"purpose-capture": null
},
"relationships": {
"versions": {
"links": {
"self": "https://localhost:5001/jobs/2/relationships/versions",
"related": "https://localhost:5001/jobs/2/versions"
}
}
},
"links": {
"self": "https://localhost:5001/jobs/2"
}
}
}
Refer to Michael Mairegger anwser to get the content of related items via include keyword.

EF Core 2.2 - can't add new record with link to already existing another one

My POCO classes:
[Table]
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<CategoryProduct> CategoryProducts { get; set; }
}
public class CategoryProduct
{
public int CategoryId { get; set; }
public int ProductId { get; set; }
public Category Category { get; set; }
public Product Product { get; set; }
}
[Table]
public class Category
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<CategoryProduct> CategoryProducts { get; set; }
}
Here's a function for inserting new records:
async Task CreateProduct(Product dto)
{
await ctx.Products.AddAsync(dto);
await ctx.SaveChangesAsync();
}
In dto I pass the following JSON:
{
"name": "Gräff Stettin",
"categoryProducts": [{
"categoryId": 1,
"productId": 1,
"category": {
"id": 1,
"name": "Drinks"
}
}, {
"categoryId": 2,
"productId": 1,
"category": {
"id": 2,
"name": "Alcohol"
}
}
]
}
As a result, at SaveChangesAsync() I get an exception with message regarding attempt to insert already existing Category. Tracing shows the following query:
INSERT INTO "Category" ("Id", "Name") VALUES (#p0, #p1);
How should I change my CreateProduct() method to avoid attempts to add categories with already existing categoryId?
you can use AutoMapper and ignore category =
var config = new MapperConfiguration(cfg => cfg.CreateMap<categoryProductsDto,categoryProducts>())
.ForMember(u => u.Category, options => options.Ignore());
and use mapper =
var mapper = config.CreateMapper();
categoryProducts entity = mapper.Map<categoryProducts>(input);
await _categoryProductsRepository.InsertAndGetIdAsync(entity);

Included property returns object that it's included to

I'm working on my side project with is a WebAPI in ASP.NET Core 2.1. I'm using Entity Framework Core 2.1.
I have a User:
public class User : Account
{
public UInt64 UserEmail { get; protected set; }
public UserImage UserImage { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
public User() { }
public User(string nick, UInt64 login, byte[] salt, byte[] passwordHash,
string restoreKey, UInt64 userEmail) : base(nick, login, salt, passwordHash, restoreKey)
{
UserEmail = userEmail;
Role = "user";
}
public void Update(UInt64 login, UInt64 userEmail)
{
Login = login;
UserEmail = userEmail;
UpdatedAt = DateTime.UtcNow;
}
public void UpdatePassword(byte[] newPassword)
{
PasswordHash = newPassword;
UpdatedAt = DateTime.UtcNow;
}
}
and UserImage:
public class UserImage : Image
{
public int? UserRef { get; set; }
public virtual User User { get; set; }
public UserImage() : base() { }
public UserImage(string content) : base(content) { }
}
In my DbContext I have something like this:
modelBuilder.Entity<User>()
.HasOne(x => x.UserImage)
.WithOne(y => y.User)
.HasForeignKey<UserImage>(y => y.UserRef)
.IsRequired(false);
I have a method in my service that returns a user with his image:
public async Task<User> GetAsync(int id)
{
var user = await _context.Users.GetById(id)
.Include(x => x.UserImage)
.SingleOrDefaultAsync();
if (user == null)
throw new CorruptedOperationException("Invalid id");
return user;
}
The problem is that when I use a Postman to get a user I get that kind of response:
{
"userEmail": 2606810040825320252,
"userImage": {
"userRef": 16,
"user": {
"userEmail": 2606810040825320252,
"recipes": null,
"nick": "MadBear123",
"login": 10458175107962595193,
"salt": "Fv/S1pnpu1u0RA6RxE1wfwCmqhbkb0Fu0W2sOuFgv//PHyizyPtmuaX8OtYkCgSJPlKMGmE2qFgg2rgs70Ee9bbMU26iVhtIApqV/Zxac54P9EXBvgkAXede3YHzSPzHkvGz3WchUUDIQqHF+EmdvPT9KuYR1Djgywxh0bDbSJk=",
"passwordHash": "Yk5S3jutaKJpwSQoRH0nk2At3nYL/Wzi+8QGRFOZuByi54o+YJHuhPYRMMSG3Vmimv1UMRWe+VA8ym2xQxoEJA==",
"role": "user",
"restoreKey": "Vub!#g17#kcP",
"id": 16,
"createdAt": "2018-10-14T11:11:43.9902857",
"updatedAt": "2018-10-14T11:11:43.990384"
},
"imageContent": "/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDjZ/jPZ6rHocDfFz4iNI19b3BU+FdFjYiEho2/5A6l8Mi53Eg4bIbkj3Sx/bi0/T/2dde0CP8AaL1xdvhK+i+w3EeiQCeR4r7bA2NMj3rtnLhScEyLtJZRt+a/2fH8S/Ha5t77wn4x+Hfiqy0q8neCHRrW8vL2dTpuq3bhbeOOWbcqQNshdDNI8kaRqzAkYXiT9tDxF8DLbxlp+peE9b8davp8b6fdaB4ZsL9LqKFZJI57m9lubJhp6o6JGYZojch2w8UIwzEpSneEd9OtjOPJDV7el/yP2V+Hv7VvhvxLrhcftfaTHYl5CkWpXfhhHwJJFCsBBG4OAp6Y57cVjfAL9rHwj4Y/Zx+HMM37YXwb0KSPw7pkLWep3OjF4X+zQp5J/wBJjbcGIXB5yQOtfLn7FH/BbZpPA0eqN8N/EXgLVoI7u5utD8f6nqVrDeRxh5B/Z15baNLFdyNiSMwlIpt6/LG6q7pa0T/gtFefD79kf4e3uo6h8CdBsdA8N2S2clz8ablL7Xo47GMFhpy6FJM8i5IKhSySoACzqm6fehG1rtFq0pXvZH4L/sdftNWH7OOo61f65pt94g0W7sWsY9OsPEUmky3N23EMjT2zrcxLFG9w6tGrK0gjjcFJHFdR8ef2620zXZtY+EPi/wCLWh3HisTy+JLbxFqqXVxb3MgdHe0voPLdoZI3QFJIw6vACXkDkD550T4a61qHmC3jhZmwCd3T9K0JPgT4vZvlsvM3DGVfj9a6oymlaK179TCXI37z07dD6k+B3/BT/VPF+ixt8dvGnxw+JMvhXVDqnhfQtP8AEUVnZ3FxPb/Zp5r69niuLhsQBo4lEbGP7ROysm+RJfIf21v2pNU/a++KknjTVJLiBZ4I9IhtL3xFNq1zp0NuCY4fOu5ZLuaNVkAEk5JJ3ruYoSeb8IfslfEDxEFa2t7W3C95p9nqewNaXxW/Z48caV4b0e21OOzm/s8zKskV2824OwY/KVG0jpkZJGAeAMaSjVlG8l87av5mcalGMrRa9L6L0Wx//9k=",
"id": 10,
"createdAt": "2018-10-16T05:45:42.4644513",
"updatedAt": "2018-10-16T05:45:42.4645292"
},
"recipes": null,
"nick": "MadBear123",
"login": 10458175107962595193,
"salt": "Fv/S1pnpu1u0RA6RxE1wfwCmqhbkb0Fu0W2sOuFgv//PHyizyPtmuaX8OtYkCgSJPlKMGmE2qFgg2rgs70Ee9bbMU26iVhtIApqV/Zxac54P9EXBvgkAXede3YHzSPzHkvGz3WchUUDIQqHF+EmdvPT9KuYR1Djgywxh0bDbSJk=",
"passwordHash": "Yk5S3jutaKJpwSQoRH0nk2At3nYL/Wzi+8QGRFOZuByi54o+YJHuhPYRMMSG3Vmimv1UMRWe+VA8ym2xQxoEJA==",
"role": "user",
"restoreKey": "Vub!#g17#kcP",
"id": 16,
"createdAt": "2018-10-14T11:11:43.9902857",
"updatedAt": "2018-10-14T11:11:43.990384"
}
Like you can see I get a user with his image and in this image i get (again) this user.
I would like to get response like this:
{
"userEmail": 2606810040825320252,
"userImage": {
"userRef": 16,
"user": null,
"imageContent": "/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDjZ/jPZ6rHocDfFz4iNI19b3BU+FdFjYiEho2/5A6l8Mi53Eg4bIbkj3Sx/bi0/T/2dde0CP8AaL1xdvhK+i+w3EeiQCeR4r7bA2NMj3rtnLhScEyLtJZRt+a/2fH8S/Ha5t77wn4x+Hfiqy0q8neCHRrW8vL2dTpuq3bhbeOOWbcqQNshdDNI8kaRqzAkYXiT9tDxF8DLbxlp+peE9b8davp8b6fdaB4ZsL9LqKFZJI57m9lubJhp6o6JGYZojch2w8UIwzEpSneEd9OtjOPJDV7el/yP2V+Hv7VvhvxLrhcftfaTHYl5CkWpXfhhHwJJFCsBBG4OAp6Y57cVjfAL9rHwj4Y/Zx+HMM37YXwb0KSPw7pkLWep3OjF4X+zQp5J/wBJjbcGIXB5yQOtfLn7FH/BbZpPA0eqN8N/EXgLVoI7u5utD8f6nqVrDeRxh5B/Z15baNLFdyNiSMwlIpt6/LG6q7pa0T/gtFefD79kf4e3uo6h8CdBsdA8N2S2clz8ablL7Xo47GMFhpy6FJM8i5IKhSySoACzqm6fehG1rtFq0pXvZH4L/sdftNWH7OOo61f65pt94g0W7sWsY9OsPEUmky3N23EMjT2zrcxLFG9w6tGrK0gjjcFJHFdR8ef2620zXZtY+EPi/wCLWh3HisTy+JLbxFqqXVxb3MgdHe0voPLdoZI3QFJIw6vACXkDkD550T4a61qHmC3jhZmwCd3T9K0JPgT4vZvlsvM3DGVfj9a6oymlaK179TCXI37z07dD6k+B3/BT/VPF+ixt8dvGnxw+JMvhXVDqnhfQtP8AEUVnZ3FxPb/Zp5r69niuLhsQBo4lEbGP7ROysm+RJfIf21v2pNU/a++KknjTVJLiBZ4I9IhtL3xFNq1zp0NuCY4fOu5ZLuaNVkAEk5JJ3ruYoSeb8IfslfEDxEFa2t7W3C95p9nqewNaXxW/Z48caV4b0e21OOzm/s8zKskV2824OwY/KVG0jpkZJGAeAMaSjVlG8l87av5mcalGMrRa9L6L0Wx//9k=",
"id": 10,
"createdAt": "2018-10-16T05:45:42.4644513",
"updatedAt": "2018-10-16T05:45:42.4645292"
},
"recipes": null,
"nick": "MadBear123",
"login": 10458175107962595193,
"salt": "Fv/S1pnpu1u0RA6RxE1wfwCmqhbkb0Fu0W2sOuFgv//PHyizyPtmuaX8OtYkCgSJPlKMGmE2qFgg2rgs70Ee9bbMU26iVhtIApqV/Zxac54P9EXBvgkAXede3YHzSPzHkvGz3WchUUDIQqHF+EmdvPT9KuYR1Djgywxh0bDbSJk=",
"passwordHash": "Yk5S3jutaKJpwSQoRH0nk2At3nYL/Wzi+8QGRFOZuByi54o+YJHuhPYRMMSG3Vmimv1UMRWe+VA8ym2xQxoEJA==",
"role": "user",
"restoreKey": "Vub!#g17#kcP",
"id": 16,
"createdAt": "2018-10-14T11:11:43.9902857",
"updatedAt": "2018-10-14T11:11:43.990384"
}
Or something similar. I tried to work my way with Include() and (or without) virtual but I ended-up with this.
UPDATE
Definition for GetById():
public static IQueryable<User> GetById(this IQueryable<User> value,int id)
=> value.Where(x => x.Id == id);
You can find full project on GitHub. I'm working on branch MB#20.
For looping reference, you could not control it in EF Core.
For general handling, we configure it in Startup.cs like
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json
.ReferenceLoopHandling.Ignore;
options.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
});
And usecase:
var user = await _userService.GetAsync(id);
return Ok(user);
For your issue, you modified user before return by return Ok(user.ShapeData(fields)); which made JsonSerialize fail to know its the loop reference.
For a workaround, before converting to ExpandoObject, handle the loop reference like
public static ExpandoObject ShapeData<TSource>(this TSource source, string fields)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
var result =JsonConvert.SerializeObject(source, new JsonSerializerSettings{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
} );
source = JsonConvert.DeserializeObject<TSource>(result);
var dataShapedObject = new ExpandoObject();
//your rest code
return dataShapedObject;
}
Update
For null in UserImage, it is caused by you specify the properties as public string ImageContent { get; protected set; }.
Try to custom DefaultContractResolver to deserialize the protected properties.
IncludePrivateStateContractResolver.cs
public class IncludePrivateStateContractResolver : DefaultContractResolver
{
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
{
const BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
var properties = objectType.GetProperties(BindingFlags);//.Where(p => p.HasSetter() && p.HasGetter());
var fields = objectType.GetFields(BindingFlags);
var allMembers = properties.Cast<MemberInfo>().Union(fields);
return allMembers.ToList();
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
if (!prop.Writable)
{
var property = member as PropertyInfo;
if (property != null)
{
prop.Writable = property.HasSetter();
}
else
{
var field = member as FieldInfo;
if (field != null)
{
prop.Writable = true;
}
}
}
if (!prop.Readable)
{
var field = member as FieldInfo;
if (field != null)
{
prop.Readable = true;
}
}
return prop;
}
}
public static class TypeExtensions
{
public static bool HasSetter(this PropertyInfo property)
{
//In this way we can check for private setters in base classes
return property.DeclaringType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.Any(m => m.Name == "set_" + property.Name);
}
public static bool HasGetter(this PropertyInfo property)
{
//In this way we can check for private getters in base classes
return property.DeclaringType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.Any(m => m.Name == "get_" + property.Name);
}
}
usecase:
public static ExpandoObject ShapeData<TSource>(this TSource source, string fields)
{
var soureType = source.GetType();
if (source == null)
{
throw new ArgumentNullException("source");
}
var serializeSettings = new JsonSerializerSettings{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var deserializeSettings = new JsonSerializerSettings{
ContractResolver = new IncludePrivateStateContractResolver()
};
var result =JsonConvert.SerializeObject(source, serializeSettings );
var castType = JsonConvert.DeserializeObject<TSource>(result, deserializeSettings);
var dataShapedObject = new ExpandoObject();
I managed to fix my problem. I changed all private setters in models properties to public an used Tao Zhou original answer.
My ShapeData method:
public static ExpandoObject ShapeData<TSource>(this TSource source, string fields)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
var result = JsonConvert.SerializeObject(source, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
source = JsonConvert.DeserializeObject<TSource>(result);
var dataShapedObject = new ExpandoObject();
// rest of my code
}
User:
public class User : Account
{
public UInt64 UserEmail { get; set; }
public UserImage UserImage { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
// rest of the code
}
UserImage:
public class UserImage : Image
{
public int? UserRef { get; set; }
public virtual User User { get; set; }
public UserImage() : base() { }
public UserImage(string content) : base(content) { }
}
Example of the returned data:
{
"userEmail": 2606810040825320252,
"userImage": {
"userRef": 16,
"user": null,
"imageContent": "/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDjZ/jPZ6rHocDfFz4iNI19b3BU+FdFjYiEho2/5A6l8Mi53Eg4bIbkj3Sx/bi0/T/2dde0CP8AaL1xdvhK+i+w3EeiQCeR4r7bA2NMj3rtnLhScEyLtJZRt+a/2fH8S/Ha5t77wn4x+Hfiqy0q8neCHRrW8vL2dTpuq3bhbeOOWbcqQNshdDNI8kaRqzAkYXiT9tDxF8DLbxlp+peE9b8davp8b6fdaB4ZsL9LqKFZJI57m9lubJhp6o6JGYZojch2w8UIwzEpSneEd9OtjOPJDV7el/yP2V+Hv7VvhvxLrhcftfaTHYl5CkWpXfhhHwJJFCsBBG4OAp6Y57cVjfAL9rHwj4Y/Zx+HMM37YXwb0KSPw7pkLWep3OjF4X+zQp5J/wBJjbcGIXB5yQOtfLn7FH/BbZpPA0eqN8N/EXgLVoI7u5utD8f6nqVrDeRxh5B/Z15baNLFdyNiSMwlIpt6/LG6q7pa0T/gtFefD79kf4e3uo6h8CdBsdA8N2S2clz8ablL7Xo47GMFhpy6FJM8i5IKhSySoACzqm6fehG1rtFq0pXvZH4L/sdftNWH7OOo61f65pt94g0W7sWsY9OsPEUmky3N23EMjT2zrcxLFG9w6tGrK0gjjcFJHFdR8ef2620zXZtY+EPi/wCLWh3HisTy+JLbxFqqXVxb3MgdHe0voPLdoZI3QFJIw6vACXkDkD550T4a61qHmC3jhZmwCd3T9K0JPgT4vZvlsvM3DGVfj9a6oymlaK179TCXI37z07dD6k+B3/BT/VPF+ixt8dvGnxw+JMvhXVDqnhfQtP8AEUVnZ3FxPb/Zp5r69niuLhsQBo4lEbGP7ROysm+RJfIf21v2pNU/a++KknjTVJLiBZ4I9IhtL3xFNq1zp0NuCY4fOu5ZLuaNVkAEk5JJ3ruYoSeb8IfslfEDxEFa2t7W3C95p9nqewNaXxW/Z48caV4b0e21OOzm/s8zKskV2824OwY/KVG0jpkZJGAeAMaSjVlG8l87av5mcalGMrRa9L6L0Wx//9k=",
"id": 10,
"createdAt": "2018-10-16T05:45:42.4644513",
"updatedAt": "2018-10-16T05:45:42.4645292"
},
"recipes": null,
"nick": "MadBear123",
"login": 10458175107962595193,
"salt": "Fv/S1pnpu1u0RA6RxE1wfwCmqhbkb0Fu0W2sOuFgv//PHyizyPtmuaX8OtYkCgSJPlKMGmE2qFgg2rgs70Ee9bbMU26iVhtIApqV/Zxac54P9EXBvgkAXede3YHzSPzHkvGz3WchUUDIQqHF+EmdvPT9KuYR1Djgywxh0bDbSJk=",
"passwordHash": "Yk5S3jutaKJpwSQoRH0nk2At3nYL/Wzi+8QGRFOZuByi54o+YJHuhPYRMMSG3Vmimv1UMRWe+VA8ym2xQxoEJA==",
"role": "user",
"restoreKey": "Vub!#g17#kcP",
"id": 16,
"createdAt": "2018-10-14T11:11:43.9902857",
"updatedAt": "2018-10-14T11:11:43.990384"
}

How to get response in json with reference key models in WCF?

I want to get the response of the Model & reference model in json format from the Restful WCF service..
My model as below
// Role : User
[DataContract]
public class User
{
[DataMember]
public int Id {get; set;}
[DataMember]
public String Email { get; set; }
[DataMember]
public String Name { get; set; }
[DataMember]
public String Password { get; set; }
[DataMember]
public String Designation { get; set; }
}
// Role : Model
[DataContract]
public class Role
{
public Role()
{ }
[DataMember]
public int Id { get; set; }
[DataMember]
public String Name { get; set; }
}
// UserRole : Model
[DataContract]
public class UserRole
{
public UserRole()
{}
[DataMember]
public int Id { get; set; }
[DataMember]
public int UserId { get; set; }
[DataMember]
public int RoleId { get; set; }
[DataMember]
public int Level { get; set; }
}
Here what exactly I want to do.
when any client call method
Method: AssignRoleToUser with Form parameter "UserID" & "RoleId"
at that time I inserting new record to UserRole Entity and return object with json.
currently below JSON string output as response.
Current Output
{
"Id": 2,
"Level": 0,
"RoleId": 3,
"UserId": 2
}
I want like below
{
"Id": 2,
"Level": 0,
"RoleId": [
{
"Id": 5,
"Name": "Project Leader"
}
],
"UserId": [
{
"Designation": "System User",
"Email": "abhishek#domain.com",
"Gender": 0,
"Id": 1,
"Name": "System User",
"Password": null
}
]
}
Thanks