Skip to content

Commit

Permalink
Don't require entityset when writing responses (#3137)
Browse files Browse the repository at this point in the history
* Don't require entityset when writing responses

* Clean up code to remove throwIfMissing and address review comments.
  • Loading branch information
mikepizzo authored Dec 4, 2024
1 parent 9ce546b commit 6f5bc5e
Show file tree
Hide file tree
Showing 22 changed files with 128 additions and 162 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ internal ConventionalODataEntityMetadataBuilder(UriResolver resolver, string ent
/// </returns>
internal override Uri GetEditLink()
{
if(this.entitySetName == null)
{
return null;
}

Uri entitySetUri = this.uriBuilder.BuildEntitySetUri(this.baseUri, this.entitySetName);
Uri editLink = this.uriBuilder.BuildEntityInstanceUri(entitySetUri, this.entityInstance);
return editLink;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,12 @@ internal override string GetOperationTitle(string operationName)
/// </returns>
internal override bool TryGetIdForSerialization(out Uri id)
{
if(this.ResourceMetadataContext.TypeContext.NavigationSourceName == null)
{
id = null;
return false;
}

id = this.ResourceMetadataContext.Resource.IsTransient ? null : this.GetId();
return true;
}
Expand All @@ -405,10 +411,13 @@ private Uri ComputeEditLink()
{
Uri uri = this.ResourceMetadataContext.Resource.HasNonComputedId ? this.ResourceMetadataContext.Resource.NonComputedId : this.ComputedId;

Debug.Assert(this.ResourceMetadataContext != null && this.ResourceMetadataContext.TypeContext != null, "this.resourceMetadataContext != null && this.resourceMetadataContext.TypeContext != null");
if (this.ResourceMetadataContext.ActualResourceTypeName != this.ResourceMetadataContext.TypeContext.NavigationSourceEntityTypeName)
if (uri != null)
{
uri = this.UriBuilder.AppendTypeSegment(uri, this.ResourceMetadataContext.ActualResourceTypeName);
Debug.Assert(this.ResourceMetadataContext != null && this.ResourceMetadataContext.TypeContext != null, "this.resourceMetadataContext != null && this.resourceMetadataContext.TypeContext != null");
if (this.ResourceMetadataContext.ActualResourceTypeName != this.ResourceMetadataContext.TypeContext.NavigationSourceEntityTypeName)
{
uri = this.UriBuilder.AppendTypeSegment(uri, this.ResourceMetadataContext.ActualResourceTypeName);
}
}

return uri;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,11 @@ private void ComputeAndCacheId()
case EdmNavigationSourceKind.Singleton:
uri = this.ComputeIdForSingleton();
break;
// Treat UnknownEntitySet as a containment
case EdmNavigationSourceKind.UnknownEntitySet:
case EdmNavigationSourceKind.ContainedEntitySet:
uri = this.ComputeIdForContainment();
break;
case EdmNavigationSourceKind.UnknownEntitySet:
throw new ODataException(Strings.ODataMetadataBuilder_UnknownEntitySet(this.ResourceMetadataContext.TypeContext.NavigationSourceName));
default:
uri = this.ComputeId();
break;
Expand All @@ -152,7 +152,7 @@ private void ComputeAndCacheId()
/// </returns>
private Uri ComputeId()
{
if (this.ResourceMetadataContext.KeyProperties.Any())
if (this.ResourceMetadataContext.TypeContext.NavigationSourceName != null && this.ResourceMetadataContext.KeyProperties.Any())
{
Uri uri = this.UriBuilder.BuildBaseUri();
uri = this.UriBuilder.BuildEntitySetUri(uri, this.ResourceMetadataContext.TypeContext.NavigationSourceName);
Expand Down
3 changes: 1 addition & 2 deletions src/Microsoft.OData.Core/Evaluation/ODataMetadataContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,7 @@ public ODataResourceMetadataBuilder GetResourceMetadataBuilderForReader(IODataJs
IEdmEntityType navigationSourceElementType = this.edmTypeResolver.GetElementType(navigationSource);
IODataResourceTypeContext typeContext =
ODataResourceTypeContext.Create( /*serializationInfo*/
null, navigationSource, navigationSourceElementType, resourceState.ResourceTypeFromMetadata ?? resourceState.ResourceType,
/*throwIfMissingTypeInfo*/ true);
null, navigationSource, navigationSourceElementType, resourceState.ResourceTypeFromMetadata ?? resourceState.ResourceType);

IODataResourceMetadataContext resourceMetadataContext = ODataResourceMetadataContext.Create(
resource,
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.OData.Core/Json/ODataJsonWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1838,7 +1838,7 @@ await this.WriteV4DeletedEntryContentsAsync(resource)
string expectedNavigationSource = deltaResourceSetScope?.NavigationSource?.Name;
string currentNavigationSource = resource.SerializationInfo?.NavigationSourceName ?? resourceScope.NavigationSource?.Name;

if (String.IsNullOrEmpty(currentNavigationSource) || currentNavigationSource != expectedNavigationSource)
if (!String.IsNullOrEmpty(currentNavigationSource) && currentNavigationSource != expectedNavigationSource)
{
Debug.Assert(this.ScopeLevel <= 3, "Writing a nested deleted resource of the wrong type should already have been caught.");

Expand Down
41 changes: 28 additions & 13 deletions src/Microsoft.OData.Core/ODataContextUriBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,25 +172,40 @@ private Uri CreateFromContextUrlInfo(ODataContextUrlInfo info)
}
else
{
// No path information
switch (info.DeltaKind)
{
case ODataDeltaKind.ResourceSet:
return new Uri(ODataConstants.ContextUriFragmentIndicator + ODataConstants.DeltaResourceSet, UriKind.Relative);
case ODataDeltaKind.DeletedEntry:
return new Uri(ODataConstants.ContextUriFragmentIndicator + ODataConstants.DeletedEntry, UriKind.Relative);
case ODataDeltaKind.Link:
return new Uri(ODataConstants.ContextUriFragmentIndicator + ODataConstants.DeltaLink, UriKind.Relative);
case ODataDeltaKind.DeletedLink:
return new Uri(ODataConstants.ContextUriFragmentIndicator + ODataConstants.DeletedLink, UriKind.Relative);
}

if (!string.IsNullOrEmpty(info.TypeName))
{ // #TypeName
builder.Append(info.TypeName);

switch (info.DeltaKind)
{
case ODataDeltaKind.ResourceSet:
builder.Append(ODataConstants.UriSegmentSeparatorChar + ODataConstants.DeltaResourceSet);
break;
case ODataDeltaKind.DeletedEntry:
builder.Append(ODataConstants.UriSegmentSeparatorChar + ODataConstants.DeletedEntry);
break;
case ODataDeltaKind.Link:
builder.Append(ODataConstants.UriSegmentSeparatorChar + ODataConstants.DeltaLink);
break;
case ODataDeltaKind.DeletedLink:
builder.Append(ODataConstants.UriSegmentSeparatorChar + ODataConstants.DeletedLink);
break;
}
}
else
{
switch (info.DeltaKind)
{
case ODataDeltaKind.ResourceSet:
return new Uri(ODataConstants.ContextUriFragmentIndicator + ODataConstants.DeltaResourceSet, UriKind.Relative);
case ODataDeltaKind.DeletedEntry:
return new Uri(ODataConstants.ContextUriFragmentIndicator + ODataConstants.DeletedEntry, UriKind.Relative);
case ODataDeltaKind.Link:
return new Uri(ODataConstants.ContextUriFragmentIndicator + ODataConstants.DeltaLink, UriKind.Relative);
case ODataDeltaKind.DeletedLink:
return new Uri(ODataConstants.ContextUriFragmentIndicator + ODataConstants.DeletedLink, UriKind.Relative);
}

return null;
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/Microsoft.OData.Core/ODataContextUrlInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,14 @@ internal static ODataContextUrlInfo Create(ODataResourceTypeContext typeContext,
{
Debug.Assert(typeContext != null, "typeContext != null");

string typeName = typeContext.NavigationSourceEntityTypeName ?? typeContext.ExpectedResourceTypeName;

ODataContextUrlInfo contextUriInfo = new ODataContextUrlInfo()
{
IsUnknownEntitySet = typeContext.NavigationSourceKind == EdmNavigationSourceKind.UnknownEntitySet,
NavigationSource = typeContext.NavigationSourceName,
TypeCast = typeContext.NavigationSourceEntityTypeName == typeContext.ExpectedResourceTypeName ? null : typeContext.ExpectedResourceTypeName,
TypeName = typeContext.NavigationSourceEntityTypeName,
TypeCast = typeName == typeContext.ExpectedResourceTypeName ? null : typeContext.ExpectedResourceTypeName,
TypeName = EdmLibraryExtensions.GetCollectionTypeName(typeName),
IncludeFragmentItemSelector = kind == ODataDeltaKind.Resource && typeContext.NavigationSourceKind != EdmNavigationSourceKind.Singleton,
DeltaKind = kind,
NavigationPath = ComputeNavigationPath(typeContext.NavigationSourceKind, null, typeContext.NavigationSourceName),
Expand Down
53 changes: 9 additions & 44 deletions src/Microsoft.OData.Core/ODataResourceTypeContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,54 +25,36 @@ internal class ODataResourceTypeContext : IODataResourceTypeContext
/// </summary>
protected string expectedResourceTypeName;

/// <summary>
/// If true, throw if any of the set or type name cannot be determined; if false, return null when any of the set or type name cannot determined.
/// </summary>
private readonly bool throwIfMissingTypeInfo;

/// <summary>
/// Constructs an instance of <see cref="ODataResourceTypeContext"/>.
/// </summary>
/// <param name="throwIfMissingTypeInfo">If true, throw if any of the set or type name cannot be determined; if false, return null when any of the set or type name cannot determined.</param>
private ODataResourceTypeContext(bool throwIfMissingTypeInfo)
private ODataResourceTypeContext()
{
this.throwIfMissingTypeInfo = throwIfMissingTypeInfo;
}

/// <summary>
/// Constructs an instance of <see cref="ODataResourceTypeContext"/>.
/// </summary>
/// <param name="expectedResourceType">The expected resource type of resource set or resource.</param>
/// <param name="throwIfMissingTypeInfo">If true, throw if any of the set or type name cannot be determined; if false, return null when any of the set or type name cannot determined.</param>
private ODataResourceTypeContext(IEdmStructuredType expectedResourceType, bool throwIfMissingTypeInfo)
private ODataResourceTypeContext(IEdmStructuredType expectedResourceType)
{
this.expectedResourceType = expectedResourceType;
this.throwIfMissingTypeInfo = throwIfMissingTypeInfo;
}

/// <summary>
/// The navigation source name of the resource set or resource.
/// </summary>
public virtual string NavigationSourceName
{
get { return this.ValidateAndReturn(default(string)); }
}
public virtual string NavigationSourceName { get; }

/// <summary>
/// The entity type name of the navigation source of the resource set or resource.
/// </summary>
public virtual string NavigationSourceEntityTypeName
{
get { return this.ValidateAndReturn(default(string)); }
}
public virtual string NavigationSourceEntityTypeName { get; }

/// <summary>
/// The full type name of the navigation source of the resource set or resource.
/// </summary>
public virtual string NavigationSourceFullTypeName
{
get { return this.ValidateAndReturn(default(string)); }
}
public virtual string NavigationSourceFullTypeName { get; }

/// <summary>
/// The kind of the navigation source of the resource set or resource.
Expand Down Expand Up @@ -133,9 +115,8 @@ public virtual bool IsMediaLinkEntry
/// <param name="navigationSource">The navigation source of the resource set or resource.</param>
/// <param name="navigationSourceEntityType">The entity type of the navigation source.</param>
/// <param name="expectedResourceType">The expected structured type of the resource set or resource.</param>
/// <param name="throwIfMissingTypeInfo">If true, throw if any of the set or type name cannot be determined; if false, return null when any of the set or type name cannot determined.</param>
/// <returns>A new instance of <see cref="ODataResourceTypeContext"/>.</returns>
internal static ODataResourceTypeContext Create(ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType navigationSourceEntityType, IEdmStructuredType expectedResourceType, bool throwIfMissingTypeInfo)
internal static ODataResourceTypeContext Create(ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType navigationSourceEntityType, IEdmStructuredType expectedResourceType)
{
if (serializationInfo != null)
{
Expand All @@ -154,23 +135,7 @@ internal static ODataResourceTypeContext Create(ODataResourceSerializationInfo s
return new ODataResourceTypeContextWithModel(navigationSource, navigationSourceEntityType, expectedResourceType);
}

return new ODataResourceTypeContext(expectedResourceType, throwIfMissingTypeInfo);
}

/// <summary>
/// Validate and return the given value.
/// </summary>
/// <typeparam name="T">The type of the value to validate.</typeparam>
/// <param name="value">The value to validate.</param>
/// <returns>The return value.</returns>
private T ValidateAndReturn<T>(T value) where T : class
{
if (this.throwIfMissingTypeInfo && value == null)
{
throw new ODataException(Strings.ODataResourceTypeContext_MetadataOrSerializationInfoMissing);
}

return value;
return new ODataResourceTypeContext(expectedResourceType);
}

/// <summary>
Expand All @@ -188,7 +153,7 @@ internal sealed class ODataResourceTypeContextWithoutModel : ODataResourceTypeCo
/// </summary>
/// <param name="serializationInfo">The serialization info from the resource set or resource instance.</param>
internal ODataResourceTypeContextWithoutModel(ODataResourceSerializationInfo serializationInfo)
: base(/*throwIfMissingTypeInfo*/false)
: base()
{
Debug.Assert(serializationInfo != null, "serializationInfo != null");
this.serializationInfo = serializationInfo;
Expand Down Expand Up @@ -327,7 +292,7 @@ internal sealed class ODataResourceTypeContextWithModel : ODataResourceTypeConte
/// <param name="navigationSourceEntityType">The entity type of the navigation source.</param>
/// <param name="expectedResourceType">The expected resource type of the resource set or resource.</param>
internal ODataResourceTypeContextWithModel(IEdmNavigationSource navigationSource, IEdmEntityType navigationSourceEntityType, IEdmStructuredType expectedResourceType)
: base(expectedResourceType, /*throwIfMissingTypeInfo*/false)
: base(expectedResourceType)
{
Debug.Assert(expectedResourceType != null, "expectedResourceType != null");
Debug.Assert(navigationSource != null
Expand Down
17 changes: 3 additions & 14 deletions src/Microsoft.OData.Core/ODataWriterCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4101,17 +4101,11 @@ internal ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse)
{
if (this.typeContext == null)
{
// For Entity, currently we check the navigation source.
// For Complex, we don't have navigation source, So we shouldn't check it.
// If ResourceType is not provided, serialization info or navigation source info should be provided.
bool throwIfMissingTypeInfo = writingResponse && (this.ResourceType == null || this.ResourceType.TypeKind == EdmTypeKind.Entity);

this.typeContext = ODataResourceTypeContext.Create(
this.serializationInfo,
this.NavigationSource,
EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource),
this.ResourceType,
throwIfMissingTypeInfo);
this.ResourceType);
}

return this.typeContext;
Expand Down Expand Up @@ -4270,15 +4264,11 @@ public ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse)
{
IEdmStructuredType expectedResourceType = this.ResourceTypeFromMetadata ?? this.ResourceType;

// For entity, we will check the navigation source info
bool throwIfMissingTypeInfo = writingResponse && (expectedResourceType == null || expectedResourceType.TypeKind == EdmTypeKind.Entity);

this.typeContext = ODataResourceTypeContext.Create(
this.serializationInfo,
this.NavigationSource,
EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource),
expectedResourceType,
throwIfMissingTypeInfo);
expectedResourceType);
}

return this.typeContext;
Expand Down Expand Up @@ -4379,8 +4369,7 @@ public ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse = tr
this.serializationInfo,
this.NavigationSource,
EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource),
this.fakeEntityType,
writingResponse);
this.fakeEntityType);
}

return this.typeContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1516,7 +1516,7 @@ private void CreatePropertySegment(ODataPathSegment previous, IEdmProperty prope
segment.TargetKind = RequestTargetKind.Enum;
break;
default:
Debug.Assert(property.Type.IsPrimitive() || property.Type.IsTypeDefinition(), "must be primitive type or type definition property");
Debug.Assert(property.Type.IsPrimitive() || property.Type.IsTypeDefinition() || property.Type.IsUntyped(), "must be primitive type or type definition property");
segment.TargetKind = RequestTargetKind.Primitive;
break;
}
Expand Down
Loading

0 comments on commit 6f5bc5e

Please sign in to comment.