diff --git a/Swashbuckle.OData.Tests/Fixtures/ModelSharedBetweenODataAndWebApiTests.cs b/Swashbuckle.OData.Tests/Fixtures/ModelSharedBetweenODataAndWebApiTests.cs index 8c2feea..b89db76 100644 --- a/Swashbuckle.OData.Tests/Fixtures/ModelSharedBetweenODataAndWebApiTests.cs +++ b/Swashbuckle.OData.Tests/Fixtures/ModelSharedBetweenODataAndWebApiTests.cs @@ -2,14 +2,21 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Net; +using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; using System.Web.Http.Description; using System.Web.OData; using System.Web.OData.Builder; using System.Web.OData.Extensions; +using System.Web.OData.Formatter; +using System.Web.OData.Formatter.Deserialization; +using System.Web.OData.Formatter.Serialization; using FluentAssertions; +using Microsoft.OData.Core; using Microsoft.OData.Edm; +using Microsoft.Owin; using Microsoft.Owin.Hosting; using NUnit.Framework; using Owin; @@ -43,6 +50,24 @@ public async Task It_consolidates_tags_in_final_swagger_model() await ValidationUtils.ValidateSwaggerJson(); } } + + [Test] + public async Task It_serializes_web_api_model() + { + Action config = c => c.DocumentFilter(); + using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => SharedModelsSetup.ConfigurationWithFormatters(appBuilder, config, typeof(SharedModelsSetup.SharedModelsController), typeof(SharedModelsSetup.SharedModelsWebApiController)))) + { + // Access swagger doc first + await ValidationUtils.ValidateSwaggerJson(); + + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + // Verify that the custom web api model can be serialized + var webApiResults = await httpClient.GetJsonAsync>("CustomApiModels"); + webApiResults.Should().NotBeNull(); + webApiResults.Count.Should().Be(2); + } + } } public class SharedModelsSetup @@ -61,6 +86,22 @@ public static void Configuration(IAppBuilder appBuilder, Action unitTestConfigs, params Type[] targetControllers) + { + var config = new HttpConfiguration + { + IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always + }; + + config = ConfigureWebApi(config); + + config = ConfigureOData(appBuilder, targetControllers, config, unitTestConfigs); + + config.Formatters.InsertRange(0, ODataMediaTypeFormatters.Create(new NullSerializerProvider(), new DefaultODataDeserializerProvider())); + + config.EnsureInitialized(); + } + public static HttpConfiguration ConfigureWebApi(HttpConfiguration config) { config.MapHttpAttributeRoutes(); @@ -86,6 +127,64 @@ public static IEdmModel GetEdmModel() return builder.GetEdmModel(); } + public class NullSerializerProvider : DefaultODataSerializerProvider + { + private readonly NullEntityTypeSerializer _nullEntityTypeSerializer; + + public NullSerializerProvider() + { + _nullEntityTypeSerializer = new NullEntityTypeSerializer(this); + } + + public override ODataSerializer GetODataPayloadSerializer(IEdmModel model, Type type, HttpRequestMessage request) + { + var serializer = base.GetODataPayloadSerializer(model, type, request); + if (serializer == null) + { + var functions = model.SchemaElements.Where(s => s.SchemaElementKind == EdmSchemaElementKind.Function + || s.SchemaElementKind == EdmSchemaElementKind.Action); + var isFunctionCall = false; + foreach (var f in functions) + { + // ReSharper disable once UseStringInterpolation + var fname = string.Format("{0}.{1}", f.Namespace, f.Name); + if (request.RequestUri.OriginalString.Contains(fname)) + { + isFunctionCall = true; + break; + } + } + // only, if it is not a function call + if (!isFunctionCall) + { + var response = request.GetOwinContext()?.Response; + response?.OnSendingHeaders(state => + { + ((IOwinResponse)state).StatusCode = (int)HttpStatusCode.NotFound; + }, response); + // in case you are NOT using Owin, uncomment the following and comment everything above + // HttpContext.Current.Response.StatusCode = (int)HttpStatusCode.NotFound; + } + return _nullEntityTypeSerializer; + } + return serializer; + } + } + + public class NullEntityTypeSerializer : ODataEntityTypeSerializer + { + public NullEntityTypeSerializer(ODataSerializerProvider serializerProvider) + : base(serializerProvider) + { } + public override void WriteObjectInline(object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext) + { + if (graph != null) + { + base.WriteObjectInline(graph, expectedType, writer, writeContext); + } + } + } + public class SharedModel { [Key] @@ -93,6 +192,11 @@ public class SharedModel public string Variation { get; set; } } + public class CustomApiModel + { + public int PropertyA { get; set; } + } + public class SharedModelsController : ODataController { [EnableQuery] @@ -123,6 +227,17 @@ public List Get() }; return sharedModels; } + + [Route("CustomApiModels")] + public List GetCustomApiModels() + { + var customApiModels = new List + { + new CustomApiModel {PropertyA = 1}, + new CustomApiModel {PropertyA = 2} + }; + return customApiModels; + } } } diff --git a/Swashbuckle.OData/Descriptions/ODataActionDescriptorMapperBase.cs b/Swashbuckle.OData/Descriptions/ODataActionDescriptorMapperBase.cs index dad1bc3..4f8bd01 100644 --- a/Swashbuckle.OData/Descriptions/ODataActionDescriptorMapperBase.cs +++ b/Swashbuckle.OData/Descriptions/ODataActionDescriptorMapperBase.cs @@ -94,8 +94,10 @@ private static Func CanWriteODataType(ODataActionDescr if (oDataMediaTypeFormatter != null) { - oDataMediaTypeFormatter.SetInstanceProperty("Request", oDataActionDescriptor.Request); - return mediaTypeFormatter.CanWriteType(returnType); + var mediaType = oDataMediaTypeFormatter.SupportedMediaTypes.FirstOrDefault(); + var instanceFormatter = oDataMediaTypeFormatter.GetPerRequestFormatterInstance(returnType, + oDataActionDescriptor.Request, mediaType); + return instanceFormatter.CanWriteType(returnType); } return false; }; @@ -109,8 +111,10 @@ private static Func CanReadODataType(ODataActionDescri if (oDataMediaTypeFormatter != null) { - oDataMediaTypeFormatter.SetInstanceProperty("Request", oDataActionDescriptor.Request); - return mediaTypeFormatter.CanReadType(bodyParameter.ParameterDescriptor.ParameterType); + var mediaType = oDataMediaTypeFormatter.SupportedMediaTypes.FirstOrDefault(); + var instanceFormatter = oDataMediaTypeFormatter.GetPerRequestFormatterInstance(bodyParameter.ParameterDescriptor.ParameterType, + oDataActionDescriptor.Request, mediaType); + return instanceFormatter.CanReadType(bodyParameter.ParameterDescriptor.ParameterType); } return false; };