private ResourceObject TryGetBuiltResourceObjectFor(IIdentifiable resource) { Type resourceType = resource.GetType(); ResourceContext resourceContext = ResourceContextProvider.GetResourceContext(resourceType); return(_included.SingleOrDefault(resourceObject => resourceObject.Type == resourceContext.PublicName && resourceObject.Id == resource.StringId)); }
/// <inheritdoc /> public IList <ResourceObject> Build() { if (_included.Any()) { // cleans relationship dictionaries and adds links of resources. foreach (var resourceObject in _included) { if (resourceObject.Relationships != null) { foreach (var relationshipName in resourceObject.Relationships.Keys.ToArray()) { var resourceContext = ResourceContextProvider.GetResourceContext(resourceObject.Type); var relationship = resourceContext.Relationships.Single(rel => rel.PublicName == relationshipName); if (!IsRelationshipInSparseFieldSet(relationship)) { resourceObject.Relationships.Remove(relationshipName); } } // removes relationship entries (<see cref="RelationshipEntry"/>s) if they're completely empty. var pruned = resourceObject.Relationships.Where(p => p.Value.IsPopulated || p.Value.Links != null).ToDictionary(p => p.Key, p => p.Value); if (!pruned.Any()) { pruned = null; } resourceObject.Relationships = pruned; } resourceObject.Links = _linkBuilder.GetResourceLinks(resourceObject.Type, resourceObject.Id); } return(_included.ToArray()); } return(null); }
/// <summary> /// Sets a HasOne relationship on a parsed resource. If present, also /// populates the foreign key. /// </summary> private void SetHasOneRelationship(IIdentifiable resource, PropertyInfo[] resourceProperties, HasOneAttribute attr, RelationshipEntry relationshipData) { var rio = (ResourceIdentifierObject)relationshipData.Data; var relatedId = rio?.Id; var relationshipType = relationshipData.SingleData == null ? attr.RightType : ResourceContextProvider.GetResourceContext(relationshipData.SingleData.Type).ResourceType; // this does not make sense in the following case: if we're setting the dependent of a one-to-one relationship, IdentifiablePropertyName should be null. var foreignKeyProperty = resourceProperties.FirstOrDefault(p => p.Name == attr.IdentifiablePropertyName); if (foreignKeyProperty != null) { // there is a FK from the current resource pointing to the related object, // i.e. we're populating the relationship from the dependent side. SetForeignKey(resource, foreignKeyProperty, attr, relatedId, relationshipType); } SetNavigation(resource, attr, relatedId, relationshipType); // depending on if this base parser is used client-side or server-side, // different additional processing per field needs to be executed. AfterProcessField(resource, attr, relationshipData); }
private bool IsRelationshipInSparseFieldSet(RelationshipAttribute relationship) { ResourceContext resourceContext = ResourceContextProvider.GetResourceContext(relationship.LeftType); IReadOnlyCollection <ResourceFieldAttribute> fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); return(fieldSet.Contains(relationship)); }
private bool IsRelationshipInSparseFieldSet(RelationshipAttribute relationship) { var resourceContext = ResourceContextProvider.GetResourceContext(relationship.LeftType); var fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); return(fieldSet.Contains(relationship)); }
protected ResourceContext GetExistingResourceContext(string publicName) { var resourceContext = ResourceContextProvider.GetResourceContext(publicName); if (resourceContext == null) { throw new JsonApiSerializationException("Request body includes unknown resource type.", $"Resource type '{publicName}' does not exist.", atomicOperationIndex: AtomicOperationIndex); } return(resourceContext); }
private OperationContainer ParseForRelationshipOperation(AtomicOperationObject operation, OperationKind kind, bool requireToMany) { AssertElementHasType(operation.Ref, "ref"); AssertElementHasIdOrLid(operation.Ref, "ref", true); var primaryResourceContext = GetExistingResourceContext(operation.Ref.Type); AssertCompatibleId(operation.Ref, primaryResourceContext.IdentityType); var primaryResource = ResourceFactory.CreateInstance(primaryResourceContext.ResourceType); primaryResource.StringId = operation.Ref.Id; primaryResource.LocalId = operation.Ref.Lid; var relationship = GetExistingRelationship(operation.Ref, primaryResourceContext); if (requireToMany && relationship is HasOneAttribute) { throw new JsonApiSerializationException( $"Only to-many relationships can be targeted in '{operation.Code.ToString().Camelize()}' operations.", $"Relationship '{operation.Ref.Relationship}' must be a to-many relationship.", atomicOperationIndex: AtomicOperationIndex); } var secondaryResourceContext = ResourceContextProvider.GetResourceContext(relationship.RightType); var request = new JsonApiRequest { Kind = EndpointKind.AtomicOperations, BasePath = _request.BasePath, PrimaryId = primaryResource.StringId, PrimaryResource = primaryResourceContext, SecondaryResource = secondaryResourceContext, Relationship = relationship, IsCollection = relationship is HasManyAttribute, OperationKind = kind }; _request.CopyFrom(request); _targetedFields.Relationships.Add(relationship); ParseDataForRelationship(relationship, secondaryResourceContext, operation, primaryResource); var targetedFields = new TargetedFields { Attributes = _targetedFields.Attributes.ToHashSet(), Relationships = _targetedFields.Relationships.ToHashSet() }; return(new OperationContainer(kind, primaryResource, targetedFields, request)); }
/// <summary> /// Gets the resource object for <paramref name="parent"/> by searching the included list. /// If it was not already built, it is constructed and added to the inclusion list. /// </summary> private ResourceObject GetOrBuildResourceObject(IIdentifiable parent, RelationshipAttribute relationship) { var type = parent.GetType(); var resourceName = ResourceContextProvider.GetResourceContext(type).PublicName; var entry = _included.SingleOrDefault(ro => ro.Type == resourceName && ro.Id == parent.StringId); if (entry == null) { entry = Build(parent, _fieldsToSerialize.GetAttributes(type, relationship), _fieldsToSerialize.GetRelationships(type)); _included.Add(entry); } return(entry); }
private void UpdateRelationships(ResourceObject resourceObject) { foreach (string relationshipName in resourceObject.Relationships.Keys.ToArray()) { ResourceContext resourceContext = ResourceContextProvider.GetResourceContext(resourceObject.Type); RelationshipAttribute relationship = resourceContext.Relationships.Single(rel => rel.PublicName == relationshipName); if (!IsRelationshipInSparseFieldSet(relationship)) { resourceObject.Relationships.Remove(relationshipName); } } resourceObject.Relationships = PruneRelationshipEntries(resourceObject); }
/// <summary> /// Sets a HasMany relationship. /// </summary> private void SetHasManyRelationship( IIdentifiable resource, HasManyAttribute attr, RelationshipEntry relationshipData) { if (relationshipData.Data != null) { // if the relationship is set to null, no need to set the navigation property to null: this is the default value. var relatedResources = relationshipData.ManyData.Select(rio => { var relationshipType = ResourceContextProvider.GetResourceContext(rio.Type).ResourceType; var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(relationshipType); relatedInstance.StringId = rio.Id; return(relatedInstance); }); var convertedCollection = TypeHelper.CopyToTypedCollection(relatedResources, attr.Property.PropertyType); attr.SetValue(resource, convertedCollection, ResourceFactory); } AfterProcessField(resource, attr, relationshipData); }
/// <summary> /// Searches for and parses the included relationship. /// </summary> private IIdentifiable ParseIncludedRelationship(ResourceIdentifierObject relatedResourceIdentifier) { var relatedResourceContext = ResourceContextProvider.GetResourceContext(relatedResourceIdentifier.Type); if (relatedResourceContext == null) { throw new InvalidOperationException($"Included type '{relatedResourceIdentifier.Type}' is not a registered JSON:API resource."); } var relatedInstance = ResourceFactory.CreateInstance(relatedResourceContext.ResourceType); relatedInstance.StringId = relatedResourceIdentifier.Id; var includedResource = GetLinkedResource(relatedResourceIdentifier); if (includedResource != null) { SetAttributes(relatedInstance, includedResource.Attributes, relatedResourceContext.Attributes); SetRelationships(relatedInstance, includedResource.Relationships, relatedResourceContext.Relationships); } return(relatedInstance); }
/// <summary> /// Creates an instance of the referenced type in <paramref name="data"/> /// and sets its attributes and relationships. /// </summary> /// <returns>The parsed resource.</returns> private IIdentifiable ParseResourceObject(ResourceObject data) { var resourceContext = ResourceContextProvider.GetResourceContext(data.Type); if (resourceContext == null) { throw new InvalidRequestBodyException("Payload includes unknown resource type.", $"The resource '{data.Type}' is not registered on the resource graph. " + "If you are using Entity Framework Core, make sure the DbSet matches the expected resource name. " + "If you have manually registered the resource, check that the call to Add correctly sets the public name.", null); } var resource = (IIdentifiable)ResourceFactory.CreateInstance(resourceContext.ResourceType); resource = SetAttributes(resource, data.Attributes, resourceContext.Attributes); resource = SetRelationships(resource, data.Relationships, resourceContext.Relationships); if (data.Id != null) { resource.StringId = data.Id; } return(resource); }