Skip to content

Commit

Permalink
Enable $batch request with Endpoint routing
Browse files Browse the repository at this point in the history
  • Loading branch information
xuzhg committed Mar 4, 2020
1 parent dd0d4e2 commit e5410e8
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 16 deletions.
22 changes: 11 additions & 11 deletions samples/AspNetCore3xEndpointSample.Web/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using AspNetCore3xEndpointSample.Web.Models;
using Microsoft.AspNet.OData.Batch;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNet.OData.Formatter.Deserialization;
using Microsoft.AspNet.OData.Routing.Conventions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OData.Edm;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNet.OData.Batch;
using Microsoft.AspNet.OData.Formatter.Deserialization;
using System;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.AspNetCore.Http;
using Microsoft.OData;
using System.Collections.Generic;
using Microsoft.AspNet.OData.Routing.Conventions;
using Microsoft.OData.Edm;

namespace AspNetCore3xEndpointSample.Web
{
Expand Down Expand Up @@ -48,10 +47,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

IEdmModel model = EdmModelBuilder.GetEdmModel();

app.UseRouting();

// Please add "UseODataBatching()" before "UseRouting()" to support OData $batch.
app.UseODataBatching();

app.UseRouting();

app.UseEndpoints(endpoints =>
{
endpoints.MapODataRoute(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
using Microsoft.AspNet.OData.Routing;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
#if !NETSTANDARD2_0
using Microsoft.AspNetCore.Http.Features;
#endif
using Microsoft.AspNetCore.Http.Headers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
Expand Down Expand Up @@ -285,12 +288,35 @@ internal static Uri GetODataBatchBaseUri(this HttpRequest request, string oDataR
return new Uri(request.GetDisplayUrl());
}

HttpContext context = request.HttpContext;

#if !NETSTANDARD2_0
// Here's workaround to help "EndpointLinkGenerator" to generator
ODataBatchPathMapping batchMapping = request.HttpContext.RequestServices.GetRequiredService<ODataBatchPathMapping>();
if (batchMapping.IsEndpointRouting)
{
context = new DefaultHttpContext
{
RequestServices = request.HttpContext.RequestServices,
};

IEndpointFeature endpointFeature = new ODataEndpointFeature();
endpointFeature.Endpoint = new Endpoint((d) => null, null, "anything");
context.Features.Set(endpointFeature);

context.Request.Scheme = request.Scheme;
context.Request.Host = request.Host;
}

context.Request.ODataFeature().RouteName = oDataRouteName;
#endif

// The IActionContextAccessor and ActionContext will be present after routing but not before
// GetUrlHelper only uses the HttpContext and the Router, which we have so construct a dummy
// action context.
ActionContext actionContext = new ActionContext
{
HttpContext = request.HttpContext,
HttpContext = context,
RouteData = new RouteData(),
ActionDescriptor = new ActionDescriptor()
};
Expand All @@ -316,5 +342,12 @@ internal static Uri GetODataBatchBaseUri(this HttpRequest request, string oDataR
}
return new Uri(baseAddress);
}

#if !NETSTANDARD2_0
internal class ODataEndpointFeature : IEndpointFeature
{
public Endpoint Endpoint { get; set; }
}
#endif
}
}
5 changes: 5 additions & 0 deletions src/Microsoft.AspNetCore.OData/Batch/ODataBatchPathMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public class ODataBatchPathMapping
{
private Dictionary<TemplateMatcher, string> templateMappings = new Dictionary<TemplateMatcher, string>();

/// <summary>
/// Gets/sets a boolean value indicating whether it's endpoint routing.
/// </summary>
internal bool IsEndpointRouting { get; set; } = false;

/// <summary>
/// Add a route name and template for batching.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,13 @@ private static HttpContext CreateHttpContext(HttpContext originalContext)
continue;
}

#if !NETSTANDARD2_0
if (kvp.Key == typeof(IEndpointFeature))
{
continue;
}
#endif

features[kvp.Key] = kvp.Value;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -588,15 +588,15 @@ public static IEndpointRouteBuilder MapODataRoute(this IEndpointRouteBuilder bui
ODataBatchHandler batchHandler = subServiceProvider.GetService<ODataBatchHandler>();
if (batchHandler != null)
{
// TODO: for the $batch, i need more time to refactor/test it.
// batchHandler.ODataRoute = route;
// TODO: for the $batch, need refactor/test it for more.
batchHandler.ODataRouteName = routeName;

string batchPath = String.IsNullOrEmpty(routePrefix)
? '/' + ODataRouteConstants.Batch
: '/' + routePrefix + '/' + ODataRouteConstants.Batch;

ODataBatchPathMapping batchMapping = builder.ServiceProvider.GetRequiredService<ODataBatchPathMapping>();
batchMapping.IsEndpointRouting = true;
batchMapping.AddRoute(routeName, batchPath);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ public ITestActionResult Delete([FromODataUri]int key)

public void Dispose()
{
_context.Dispose();
// DO NOT dispose _context. Otherwise the $batch will get exception to complain about the re-using the disposed object.
// _context.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNet.OData.Batch;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNet.OData.Routing;
using Microsoft.AspNet.OData.Routing.Conventions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OData;
using Microsoft.OData.Edm;
using Microsoft.Test.E2E.AspNet.OData.Common.Extensions;
using Microsoft.Test.E2E.AspNet.OData.ModelBuilder;
using Newtonsoft.Json.Linq;
using Xunit;

namespace Microsoft.Test.E2E.AspNet.OData.Endpoint
{
public class EndpointEfCoreTests : EndpointTestBase<EndpointEfCoreTests>
{
public static IEdmModel EdmModel;

private const string CustomersBaseUrl = "{0}/odata/EpCustomers";

public EndpointEfCoreTests(EndpointTestFixture<EndpointEfCoreTests> fixture)
Expand All @@ -38,7 +47,12 @@ protected static void UpdateConfigure(EndpointRouteConfiguration configuration)

configuration.MaxTop(2).Expand().Select().OrderBy().Filter();

configuration.MapODataRoute("odata", "odata", EndpointModelGenerator.GetConventionalEdmModel());
EdmModel = EndpointModelGenerator.GetConventionalEdmModel();
configuration.MapODataRoute("odata", "odata",
EdmModel,
new DefaultODataPathHandler(),
ODataRoutingConventions.CreateDefault(),
new DefaultODataBatchHandler());
}

[Fact]
Expand Down Expand Up @@ -186,6 +200,79 @@ public async Task QueryOptoinsOnEntityUsingEndpointRoutingWorks()
Assert.Equal("1", order["Id"].ToString());
Assert.Equal("104m", order["Title"].ToString());
}

[Fact]
public async Task CanReadDataInBatchUsingEndpointRouting()
{
// Arrange
var requestUri = string.Format("{0}/odata/$batch", this.BaseAddress);
Uri address = new Uri(this.BaseAddress, UriKind.Absolute);

string relativeToServiceRootUri = "EpCustomers";
string relativeToHostUri = address.LocalPath.TrimEnd(new char[] { '/' }) + "/odata/EpCustomers";
string absoluteUri = this.BaseAddress + "/odata/EpCustomers";

// Act
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
HttpContent content = new StringContent(@"
{
""requests"":[
{
""id"": ""2"",
""method"": ""get"",
""url"": """ + relativeToServiceRootUri + @""",
""headers"": { ""Accept"": ""application/json""}
},
{
""id"": ""3"",
""method"": ""get"",
""url"": """ + relativeToHostUri + @"""
},
{
""id"": ""4"",
""method"": ""get"",
""url"": """ + absoluteUri + @""",
""headers"": { ""Accept"": ""application/json""}
}
]
}");
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
request.Content = content;
HttpResponseMessage response = await Client.SendAsync(request);

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);

var stream = await response.Content.ReadAsStreamAsync();
IODataResponseMessage odataResponseMessage = new ODataMessageWrapper(stream, response.Content.Headers);
int subResponseCount = 0;
var model = EdmModel;
using (var messageReader = new ODataMessageReader(odataResponseMessage, new ODataMessageReaderSettings(), model))
{
var batchReader = messageReader.CreateODataBatchReader();
while (batchReader.Read())
{
switch (batchReader.State)
{
case ODataBatchReaderState.Operation:
var operationMessage = batchReader.CreateOperationResponseMessage();
subResponseCount++;
Assert.Equal(200, operationMessage.StatusCode);
Assert.Contains("application/json", operationMessage.Headers.Single(h => String.Equals(h.Key, "Content-Type", StringComparison.OrdinalIgnoreCase)).Value);
using (var innerMessageReader = new ODataMessageReader(operationMessage, new ODataMessageReaderSettings(), model))
{
var innerReader = innerMessageReader.CreateODataResourceSetReader();
while (innerReader.Read()) ;
}
break;
}
}
}

Assert.Equal(3, subResponseCount);
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ private void Initialize()
{
this.ClientFactory = app.ApplicationServices.GetRequiredService<IHttpClientFactory>();

// should add ODataBatch middleware before the routing middelware
app.UseODataBatching();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
Expand Down

0 comments on commit e5410e8

Please sign in to comment.