Skip to content

Commit

Permalink
Provide option for alternative form of delete link request Uri
Browse files Browse the repository at this point in the history
  • Loading branch information
gathogojr committed Oct 4, 2021
1 parent 59cc9b6 commit 94b6444
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 3 deletions.
24 changes: 21 additions & 3 deletions src/Microsoft.OData.Client/BaseSaveResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1093,7 +1093,7 @@ private static string SerializeSupportedVersions()
/// <param name="binding">The binding.</param>
/// <param name="targetResource">The target's entity descriptor.</param>
/// <returns>The original link uri or one with the target entity key appended.</returns>
private static Uri AppendTargetEntityKeyIfNeeded(Uri linkUri, LinkDescriptor binding, EntityDescriptor targetResource)
private Uri AppendTargetEntityKeyIfNeeded(Uri linkUri, LinkDescriptor binding, EntityDescriptor targetResource)
{
// To delete from a collection, we need to append the key.
// For example: if the navigation property name is "Purchases" and the resource type is Order with key '1', then this method will generate 'baseuri/Purchases(1)'
Expand All @@ -1104,8 +1104,26 @@ private static Uri AppendTargetEntityKeyIfNeeded(Uri linkUri, LinkDescriptor bin

Debug.Assert(targetResource != null, "targetResource != null");
StringBuilder builder = new StringBuilder();
builder.Append(UriUtil.UriToString(linkUri));
builder.Append(UriHelper.QUESTIONMARK + XmlConstants.HttpQueryStringId + UriHelper.EQUALSSIGN + targetResource.Identity);
string uriString = UriUtil.UriToString(linkUri);

if (this.RequestInfo.Context.DeleteLinkUriOption == DeleteLinkUriOption.RelatedKeyAsSegment)
{
// Related key segment should appear before /$ref
int indexOfLinkSegment = uriString.IndexOf(XmlConstants.UriLinkSegment, StringComparison.Ordinal);
builder.Append(indexOfLinkSegment > 0 ? uriString.Substring(0, indexOfLinkSegment - 1) : uriString);
this.RequestInfo.Context.UrlKeyDelimiter.AppendKeyExpression(targetResource.EdmValue, builder);
if (indexOfLinkSegment > 0)
{
builder.Append('/');
builder.Append(XmlConstants.UriLinkSegment);
}
}
else
{
builder.Append(uriString);
builder.Append(UriHelper.QUESTIONMARK + XmlConstants.HttpQueryStringId + UriHelper.EQUALSSIGN + targetResource.Identity);
}

return UriUtil.CreateUri(builder.ToString(), UriKind.RelativeOrAbsolute);
}

Expand Down
12 changes: 12 additions & 0 deletions src/Microsoft.OData.Client/DataServiceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ public class DataServiceContext

private ConcurrentDictionary<string, Type> resolveTypesCache = new ConcurrentDictionary<string, Type>();

/// <summary>Used to specify the option for the form of Uri to be generated for a delete link request.</summary>
private DeleteLinkUriOption deleteLinkUriOption;

#region Test hooks for header and payload verification

#pragma warning disable 0169, 0649
Expand Down Expand Up @@ -285,6 +288,7 @@ internal DataServiceContext(Uri serviceRoot, ODataProtocolVersion maxProtocolVer
this.UsingDataServiceCollection = false;
this.UsePostTunneling = false;
this.keyComparisonGeneratesFilterQuery = false;
this.deleteLinkUriOption = DeleteLinkUriOption.DollarIdQueryParam;
}

#endregion
Expand Down Expand Up @@ -689,6 +693,14 @@ public virtual bool KeyComparisonGeneratesFilterQuery
get { return this.keyComparisonGeneratesFilterQuery; }
set { this.keyComparisonGeneratesFilterQuery = value; }
}

/// <summary>Gets or sets the option for the form of Uri to be generated for a delete link request.</summary>
public virtual DeleteLinkUriOption DeleteLinkUriOption
{
get { return this.deleteLinkUriOption; }
set { this.deleteLinkUriOption = value; }
}

/// <summary>Gets or sets whether to support undeclared properties.</summary>
/// <returns>UndeclaredPropertyBehavior.</returns>
internal UndeclaredPropertyBehavior UndeclaredPropertyBehavior
Expand Down
26 changes: 26 additions & 0 deletions src/Microsoft.OData.Client/DeleteLinkUriOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//---------------------------------------------------------------------
// <copyright file="DeleteLinkUriOption.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

namespace Microsoft.OData.Client
{
/// <summary>
/// Used to specify the form of Uri to be used for a delete link request.
/// </summary>
public enum DeleteLinkUriOption
{
/// <summary>
/// Pass the id of the related entity as a query param, i.e.,
/// {ServiceUri}/{EntitySet}/{Key}/{NavigationProperty}/$ref?$id={ServiceUri}/{RelatedEntitySet}/{RelatedKey}
/// </summary>
DollarIdQueryParam = 0,

/// <summary>
/// Pass the id of the related entity as a key segment, i.e.,
/// {ServiceUri}/{EntitySet}/{Key}/{NavigationProperty}/{RelatedKey}/$ref
/// </summary>
RelatedKeyAsSegment = 1,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<Compile Include="DateTimePrimitiveTypeReference.cs" />
<Compile Include="Tests\Annotation\AnnotationTargetingOperationTestsProxy.cs" />
<Compile Include="Tests\Annotation\ClientAnnotationTests.cs" />
<Compile Include="Tests\DeleteLinkTests.cs" />
<Compile Include="Tests\CustomizedHttpWebRequestMessage.cs" />
<Compile Include="Tests\Annotation\AnnotationTestsProxy.cs" />
<Compile Include="Tests\Annotation\ClientMetadataAnnotationTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//---------------------------------------------------------------------
// <copyright file="DeleteLinkTests.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

namespace Microsoft.OData.Client.TDDUnitTests.Tests
{
using System;
using System.ComponentModel;
using Microsoft.OData.Edm;
using Xunit;

public class DeleteLinkTests
{
private const string NamespaceName = "Microsoft.OData.Client.TDDUnitTests.Tests";
private const string ServiceUri = "http://tempuri.svc";
private EdmModel model;
private TestDataServiceContext dataServiceContext;

public DeleteLinkTests()
{
this.InitializeEdmModel();
this.dataServiceContext = new TestDataServiceContext(new Uri(ServiceUri), this.model);
}

[Theory]
[InlineData(DeleteLinkUriOption.DollarIdQueryParam, "http://tempuri.svc/Customers(1)/Orders/$ref?$id=http://tempuri.svc/Orders(1)")]
[InlineData(DeleteLinkUriOption.RelatedKeyAsSegment, "http://tempuri.svc/Customers(1)/Orders(1)/$ref")]
public void ExpectedDeleteLinkUriShouldBeGenerated(DeleteLinkUriOption deleteLinkUriOption, string expectedUri)
{
this.dataServiceContext.DeleteLinkUriOption = deleteLinkUriOption;

var customer = new Customer { Id = 1 };
var order = new Order { Id = 1 };

var customerCollection = new DataServiceCollection<Customer>(
dataServiceContext, new[] { customer },
TrackingMode.AutoChangeTracking,
"Customers",
null,
null);
var orderCollection = new DataServiceCollection<Order>(
dataServiceContext,
new[] { order },
TrackingMode.AutoChangeTracking,
"Orders",
null,
null);

this.dataServiceContext.DeleteLink(customer, "Orders", order);
var saveResult = new TestSaveResult(this.dataServiceContext, "SaveChanges", SaveChangesOptions.None, null, null);

// The API does not offer an easy way to grap the created request and inspect the Uri so we ride on an extensibility hook
this.dataServiceContext.SendingRequest2 += (sender, args) =>
{
Assert.Equal(expectedUri, args.RequestMessage.Url.AbsoluteUri);
};

// If SendingRequest2 event if not fired, an exception is thrown and the test will fail
saveResult.CreateRequestAndFireSendingEvent();
}

private void InitializeEdmModel()
{
model = new EdmModel();

var orderEntityType = new EdmEntityType(NamespaceName, "Order");
orderEntityType.AddKeys(orderEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32));
model.AddElement(orderEntityType);

var customerEntityType = new EdmEntityType(NamespaceName, "Customer");
customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32));
var ordersNavProperty = customerEntityType.AddUnidirectionalNavigation(
new EdmNavigationPropertyInfo
{
Name = "Orders",
Target = orderEntityType,
TargetMultiplicity = EdmMultiplicity.Many
});
model.AddElement(customerEntityType);

var entityContainer = new EdmEntityContainer(NamespaceName, "Container");
model.AddElement(entityContainer);

var orderEntitySet = entityContainer.AddEntitySet("Orders", orderEntityType);
var customerEntitySet = entityContainer.AddEntitySet("Customers", customerEntityType);
customerEntitySet.AddNavigationTarget(ordersNavProperty, orderEntitySet);
}

[Key("Id")]
internal partial class Customer : BaseEntityType, INotifyPropertyChanged
{
public virtual int Id
{
get
{
return this._Id;
}
set
{
this.OnIdChanging(value);
this._Id = value;
this.OnIdChanged();
this.OnPropertyChanged("Id");
}
}
private int _Id;
partial void OnIdChanging(int value);
partial void OnIdChanged();

public virtual DataServiceCollection<Order> Orders
{
get
{
return this._Orders;
}
set
{
this.OnOrdersChanging(value);
this._Orders = value;
this.OnOrdersChanged();
this.OnPropertyChanged("Orders");
}
}
private DataServiceCollection<Order> _Orders = new DataServiceCollection<Order>(null, TrackingMode.None);
partial void OnOrdersChanging(DataServiceCollection<Order> value);
partial void OnOrdersChanged();

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string property)
{
if ((this.PropertyChanged != null))
{
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}

[Key("Id")]
internal partial class Order : BaseEntityType, INotifyPropertyChanged
{
public virtual int Id
{
get
{
return this._Id;
}
set
{
this.OnIdChanging(value);
this._Id = value;
this.OnIdChanged();
this.OnPropertyChanged("Id");
}
}
private int _Id;
partial void OnIdChanging(int value);
partial void OnIdChanged();

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string property)
{
if ((this.PropertyChanged != null))
{
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}

internal partial class TestDataServiceContext : DataServiceContext
{
public TestDataServiceContext(Uri serviceRoot, IEdmModel serviceModel) :
base(serviceRoot, ODataProtocolVersion.V4)
{
this.Format.UseJson(serviceModel);
}
}

internal class TestSaveResult : SaveResult
{
public TestSaveResult(DataServiceContext context, string method, SaveChangesOptions options, AsyncCallback callback, object state)
: base(context, method, options, callback, state)
{
}

internal void CreateRequestAndFireSendingEvent()
{
if (this.ChangedEntries.Count > 0)
{
if (this.ChangedEntries[0] is LinkDescriptor descriptor)
{
var requestMessageWrapper = this.CreateRequest(descriptor);
requestMessageWrapper.FireSendingEventHandlers(descriptor);

return;
}
}

throw new Exception(); // Throw exception to signal unexpected outcome
}
}
}
}

0 comments on commit 94b6444

Please sign in to comment.