Skip to content

Posting JSONAPI data not binding #237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wayne-o opened this issue Feb 28, 2018 · 22 comments
Closed

Posting JSONAPI data not binding #237

wayne-o opened this issue Feb 28, 2018 · 22 comments
Labels

Comments

@wayne-o
Copy link
Contributor

wayne-o commented Feb 28, 2018

When I have the following classes:

public class ApplicationUserViewModel : Identifiable<string>
    {
        public ApplicationUserViewModel()
        {
            Id = Guid.NewGuid().ToString();
            Roles = new List<ApplicationRoleViewModel>();
        }

        [NotMapped]
        public string StringId { get => Id; set => Id = value; }

        [Attr("username")] public string UserName { get; set; }
        [Attr("email")] public string Email { get; set; }

        [HasMany("roles")]
        public List<ApplicationRoleViewModel> Roles
        {
            get;
            set;
        }
    }

public class ApplicationRoleViewModel : Identifiable<string>
    {
        public ApplicationRoleViewModel()
        {
            Users = new List<ApplicationUserViewModel>();
            Id = Guid.NewGuid().ToString();
        }

        [NotMapped]
        public string StringId { get => Id; set => Id = value; }

        [Attr("name")]
        public string Name { get; set; }

        [HasMany("users")]
        public List<ApplicationUserViewModel> Users
        {
            get;
            set;
        }
    }

And I post the following JSON

{
  "data": {
    "id": "461a2700-114b-4667-bacf-e4425d52354f",
    "attributes": {
      "email": "[email protected]"
    },
    "relationships": {
      "roles": {
        "data": [
          {
            "type": "roles",
            "id": "67fd9442-9d8c-45de-bf42-4af8be9a8d40"
          }
        ]
      }
    },
    "type": "users"
  }
}

It doesn't deserialize properly and the model passed into the controller has empty properties.

Is there anything glaringly wrong here?

@wayne-o
Copy link
Contributor Author

wayne-o commented Feb 28, 2018

Ok so digging into this, I am seeing the following output:

 An error occurred while de-serializing the payload
JsonApiDotNetCore.Internal.JsonApiException: Failed to deserialize request body ---> System.ArgumentException: DbSet of type XXX.Api.Models.ApplicationRoleViewModel not found on the DbContext
Parameter name: T

I am not using EF for this model - is there anyway to stop it trying to do this?

@jaredcnance
Copy link
Contributor

jaredcnance commented Feb 28, 2018

Have you named your resource on the DbContext?

[Resource("roles")]

Checkout the last section here: https://json-api-dotnet.github.io/#/context-graph

But it looks like you're using a view model that probably isn't represented on the DbContext. If that's the case you have to manually register it (see the same documentation page). Eventually, we'll try autodiscovery but for now it has to be manually registered.

@wayne-o
Copy link
Contributor Author

wayne-o commented Feb 28, 2018

Yeah - but i don't want to use EF here - is there a way around this?

@wayne-o
Copy link
Contributor Author

wayne-o commented Feb 28, 2018

I have done this in startup to tell the framework about my non-ef types:

builder.AddResource<MyModel>("my-models");

@wayne-o
Copy link
Contributor Author

wayne-o commented Feb 28, 2018

I think the problem is that it drops into GenericProcessor.SetRelationships where it can only use DbContext to load the relations data.

@wayne-o
Copy link
Contributor Author

wayne-o commented Feb 28, 2018

I'm guessing that in the case of not using EF we would want to just pass forward what we have instead of trying to load them?

@wayne-o
Copy link
Contributor Author

wayne-o commented Feb 28, 2018

Or would I have to register my own IGenericProcessor - sorry this is getting noisy now :p

@jaredcnance
Copy link
Contributor

Looks like you've stumbled onto a path that was written when I assumed everything was going to be EF. This is a "bug" and does not follow the current design pattern (all EF specific interfaces should be isolated to the repository layer). For now, you would have to inject your own GenericProcessorFactory that provides a different IGenericProcessor implementation based on the related type:
https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/src/JsonApiDotNetCore/Internal/Generics/GenericProcessorFactory.cs

The GenericProcessor deviates a bit from the normal DI based service injection because the relationship types are not known until after deserialization (after the composition root has been called).

Let me know if you run into any issues here. I'll be working on an official solution. The serialization layer should not be responsible for making calls to the database.

@wayne-o
Copy link
Contributor Author

wayne-o commented Feb 28, 2018 via email

@wayne-o
Copy link
Contributor Author

wayne-o commented Apr 5, 2018

I think this has been fixed with the new IGenericProcessorFactory . - is that correct?

@wayne-o
Copy link
Contributor Author

wayne-o commented Apr 5, 2018

OK so I removed my IGenericProcessorFactory from the IOC and thought the framework would resolve my services now but I am getting this on startup:

Application startup exception: System.MissingMethodException: Method not found: 'Void JsonApiDotNetCore.Models.AttrAttribute..ctor(System.String, Boolean)'.

@jaredcnance
Copy link
Contributor

I haven't done any additional work that would solve this issue yet. However, this is a major item for me and I'd like to get it in the next release. I still need to do some design around this. It may not be easy to develop a fix in a non-breaking way.

Regarding your exception, that occurs at runtime? Can you provide the stack trace?

@wayne-o
Copy link
Contributor Author

wayne-o commented Apr 5, 2018 via email

@wayne-o
Copy link
Contributor Author

wayne-o commented Apr 6, 2018

Cancel that - seems to be broken still on one of my services - I'll revert to the old way for now

@jaredcnance
Copy link
Contributor

jaredcnance commented Apr 8, 2018

#254 is a draft of a possible solution. The root cause of the issue is that the deserializer was performing a fetch of the related entities so that Entity Framework could create the relationships betweens the models.

The new solutions proposes that we:

{
  "data": {
    "id": "461a2700-114b-4667-bacf-e4425d52354f",
    "attributes": {
      "email": "[email protected]"
    },
    "relationships": {
      "roles": {
        "data": [
          {
            "type": "roles",
            "id": "67fd9442-9d8c-45de-bf42-4af8be9a8d40"
          }
        ]
      }
    },
    "type": "users"
  }
}

to something like this in memory (of course this would not actually be JSON but the CLR's internal representation of this object):

{
    "id": "461a2700-114b-4667-bacf-e4425d52354f",
    "email": "[email protected]",
    "roles": [{
           "id": "67fd9442-9d8c-45de-bf42-4af8be9a8d40",
           "name": null,
           "someOtherAttribute": null
     }]
}

This is still a work in progress but wanted to get your opinion. Since you're not using EF, how will you be creating the relationships? Does this give you what you need?

Another issue here is that this could be a breaking change for anyone who has modified the affected repository methods and are not calling the base implementation.

@crfloyd
Copy link
Contributor

crfloyd commented Apr 24, 2018

@jaredcnance I have been working recently on integrating this library with a NoSQL datasource without EF (using MongoDb) and I have encountered some of these issues you have referenced. Because I do not want relationships to be stored as sub-documents in my db, I "dehydrate" my models prior to persisting them which entails removing all values but their Id. What I end up with is essentially foreign keys to related resources.

On the other hand, when loading a model out of the data source, if a relationship is fetched, I use the id(s) set on the relationship property to re-hydrate the model by retrieving the datasource for that model and using that to get by id. This is accomplished via a ResourceService<TResource,TKey> (implements IResourceService<T, TId> ) which takes a ResourceDataSource<TResource,TKey> in its constructor. Since each controller has a ResourceService with its own ResourceDataSource for that resource, getting a ResourceService to hydrate a resource's relationship(s) is as simple as retrieving it's associated datasource from the jsonApiContext like so:

private IResourceProvider GetDataSourceForResource(Type resourceType, Type resourceKeyType)
{
     return _jsonApiContext.GenericProcessorFactory.GetProcessor<IResourceProvider>(typeof(IResourceDataSource<,>), resourceType, resourceKeyType);
}

The IResourceProvider is just an interface implemented by all data sources that exposes a GetById method returning an object so that after retrieving the data source for the resource type, I can use the "foreign key" to call GetById on the data source then use the RelationshipAttribute.SetValue method for the relationship to set the returned value(s) from the relationship's datasource on the property.

If it is always left up to the data access layer to "hydrate" models as described, serializing/deserializing should merely be a matter of filling in the id's. If I post a new resource and it has associated relationships, I am only storing what is essentially their foreign keys and leaning on the datasource to fetch this related data when it is requested. Hope this helps. Let me know if I can provide any further clarification.

@jaredcnance
Copy link
Contributor

@crfloyd thanks for the feedback! would it be helpful for you if I pushed #254 to a pre-release NuGet feed? It'd be nice to have another set of eyes on the changes.

Also, we've started a MongoDb project that we hope to use to make this easier for your use-case. It's not very far along, but thought I should point it out.

https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb

@wayne-o
Copy link
Contributor Author

wayne-o commented Apr 25, 2018 via email

@jaredcnance
Copy link
Contributor

@wayne-o I don't currently have any requirement for it but am willing to support additional implementations. We can either move that to a different issue or start the discussion on the project Gitter.

@crfloyd
Copy link
Contributor

crfloyd commented Apr 27, 2018

@jaredcnance if you want to get the pre-release pushed out I'd be happy to take a look and see how it integrates with what I have.

@jaredcnance
Copy link
Contributor

You can get the pre-release (2.2.2-alpha1-0075) using feed https://www.myget.org/F/research-institute/api/v3/index.json

Install-Package JsonApiDotNetCore -Version 2.2.2-alpha1-0075 -Source https://www.myget.org/F/research-institute/api/v3/index.json

@wayne-o
Copy link
Contributor Author

wayne-o commented May 1, 2018

"I would be happy to submit a Marten version of this if it's any use to
anyone?"

I've been thinking about this - the only thing that I'm unsure about is how to handle includes - in Marten includes could relate to parts of the document or related documents and the syntax is very different to standard LINQ

In the case of parts of the document - I think it's probably irrelevanrt?
Related documents could also be irrelevant but I don't really know what people would expect...

jaredcnance added a commit that referenced this issue Jun 12, 2018
De-Couple deserializer from GenericProcessorFactory
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

3 participants