/// <summary> /// Customized write method (derived from ODataOutputFormatter) which translates odata context /// and uses ODataMigration serializer provider /// </summary> /// <param name="context">OutputFormatterWriteContext</param> /// <param name="selectedEncoding">Encoding</param> /// <returns>Indication that writing is complete</returns> public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { Type type = context.ObjectType; if (type == null) { throw new ArgumentNullException(nameof(type)); } type = TypeHelper.GetTaskInnerTypeOrSelf(type); HttpRequest request = context.HttpContext.Request; if (request == null) { throw new InvalidOperationException("Write to stream async must have request"); } try { HttpResponse response = context.HttpContext.Response; Uri baseAddress = GetBaseAddressInternal(request); MediaTypeHeaderValue contentType = GetContentType(response.Headers[HeaderNames.ContentType].FirstOrDefault()); Func <ODataSerializerContext> getODataSerializerContext = () => { return(new ODataSerializerContext() { Request = request, }); }; ODataSerializerProvider serializerProvider = new ODataMigrationSerializerProvider(customContainer); WriteToStream( type, context.Object, request.GetModel(), baseAddress, contentType, request.GetUrlHelper(), request, request.Headers, (services) => ODataMigrationMessageWrapper.Create(response.Body, response.Headers, null, services), (edmType) => serializerProvider.GetEdmTypeSerializer(edmType), (objectType) => serializerProvider.GetODataPayloadSerializer(objectType, request), getODataSerializerContext); return(Task.CompletedTask); } catch (Exception ex) { return(Task.FromException(ex)); } }
/// <summary> /// If the request has OData v3 headers in it, then process using V3 deserializer provider. /// Otherwise, process as base class. /// </summary> /// <param name="context">InputFormatter context</param> /// <param name="encoding">Encoding of request body</param> /// <returns>InputFormatterResult</returns> public override Task <InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } Type type = context.ModelType; if (type == null) { throw new ArgumentException("Model type for this request body is null", nameof(type)); } HttpRequest request = context.HttpContext.Request; if (request == null) { throw new ArgumentNullException(nameof(request)); } // If content length is 0 then return default value for this type RequestHeaders contentHeaders = request.GetTypedHeaders(); object defaultValue = GetDefaultValueForType(type); if (contentHeaders == null || contentHeaders.ContentLength == 0) { return(Task.FromResult(InputFormatterResult.Success(defaultValue))); } try { Func <ODataDeserializerContext> getODataDeserializerContext = () => { return(new ODataDeserializerContext { Request = request, }); }; Action <Exception> logErrorAction = (ex) => { ILogger logger = context.HttpContext.RequestServices.GetService <ILogger>(); if (logger == null) { throw ex; } logger.LogError(ex, String.Empty); }; List <IDisposable> toDispose = new List <IDisposable>(); IServiceProvider fakeProvider = (new ServiceCollection()).BuildServiceProvider(); ODataDeserializerProvider deserializerProvider = new ODataMigrationDeserializerProvider(fakeProvider); object result = ReadFromStream( type, defaultValue, request.GetModel(), GetBaseAddressInternal(request), request, () => ODataMigrationMessageWrapper.Create(request.Body, request.Headers, request.GetODataContentIdMapping(), request.GetRequestContainer()), (objectType) => deserializerProvider.GetEdmTypeDeserializer(objectType), (objectType) => deserializerProvider.GetODataDeserializer(objectType, request), getODataDeserializerContext, (disposable) => toDispose.Add(disposable), logErrorAction); foreach (IDisposable obj in toDispose) { obj.Dispose(); } return(Task.FromResult(InputFormatterResult.Success(result))); } catch (Exception ex) { context.ModelState.AddModelError(context.ModelName, ex, context.Metadata); return(Task.FromResult(InputFormatterResult.Failure())); } }
/// <summary> /// If the request has OData v3 headers in it, then process using V3 deserializer provider. /// Otherwise, process as base class. /// </summary> /// <param name="context">InputFormatter context</param> /// <param name="encoding">Encoding of request body</param> /// <returns>InputFormatterResult</returns> public override Task <InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } Type type = context.ModelType; if (type == null) { throw new ArgumentException("Model type for this request body is null", nameof(type)); } HttpRequest request = context.HttpContext.Request; if (request == null) { throw new ArgumentNullException(nameof(request)); } // Migration extension need AllowSynchronousIO to be true. (The value is false by default for .Net Core 3.0+, refer to https://github.com/dotnet/docs/issues/14835) var syncIOFeature = context.HttpContext.Features.Get <IHttpBodyControlFeature>(); if (syncIOFeature != null) { syncIOFeature.AllowSynchronousIO = true; } // If content length is 0 then return default value for this type RequestHeaders contentHeaders = request.GetTypedHeaders(); object defaultValue = GetDefaultValueForType(type); if (contentHeaders == null || contentHeaders.ContentLength == 0) { return(Task.FromResult(InputFormatterResult.Success(defaultValue))); } try { Func <ODataDeserializerContext> getODataDeserializerContext = () => { return(new ODataDeserializerContext { Request = request, }); }; Action <Exception> logErrorAction = (ex) => { ILogger logger = context.HttpContext.RequestServices.GetService <ILogger>(); if (logger == null) { throw ex; } logger.LogError(ex, String.Empty); }; List <IDisposable> disposeList = new List <IDisposable>(); IServiceProvider fakeProvider = (new ServiceCollection()).BuildServiceProvider(); ODataDeserializerProvider deserializerProvider = new ODataMigrationDeserializerProvider(fakeProvider); object result = ReadFromStream( type, defaultValue, request.GetModel(), GetBaseAddressInternal(request), request, // Use GetODataContentIdMapping() from the namespace Microsoft.AspNet.OData.Batch. Reference: https://github.com/OData/WebApi/pull/2012 () => ODataMigrationMessageWrapper.Create(request.Body, request.Headers, ODataBatchHttpRequestExtensions.GetODataContentIdMapping(request), request.GetRequestContainer()), (objectType) => deserializerProvider.GetEdmTypeDeserializer(objectType), (objectType) => deserializerProvider.GetODataDeserializer(objectType, request), getODataDeserializerContext, (disposable) => disposeList.Add(disposable), logErrorAction); foreach (IDisposable obj in disposeList) { obj.Dispose(); } return(Task.FromResult(InputFormatterResult.Success(result))); } catch (Exception ex) { context.ModelState.AddModelError(context.ModelName, ex, context.Metadata); return(Task.FromResult(InputFormatterResult.Failure())); } }
/// <summary> /// Read and serialize outgoing object to HTTP request stream. /// </summary> internal static void WriteToStream( Type type, object value, IEdmModel model, Uri baseAddress, MediaTypeHeaderValue contentType, IUrlHelper internalUrlHelper, HttpRequest internalRequest, IHeaderDictionary internalRequestHeaders, Func <IServiceProvider, ODataMigrationMessageWrapper> getODataMessageWrapper, Func <IEdmTypeReference, ODataSerializer> getEdmTypeSerializer, Func <Type, ODataSerializer> getODataPayloadSerializer, Func <ODataSerializerContext> getODataSerializerContext) { if (model == null) { throw new InvalidOperationException("Request must have model"); } ODataSerializer serializer = GetSerializer(type, value, internalRequest, getEdmTypeSerializer, getODataPayloadSerializer); // special case: if the top level serializer is an ODataPrimitiveSerializer then swap it out for an ODataMigrationPrimitiveSerializer // This only applies to the top level because inline primitives are translated but top level primitives are not, unless we use a customized serializer. if (serializer is ODataPrimitiveSerializer) { serializer = new ODataMigrationPrimitiveSerializer(); } ODataPath path = internalRequest.ODataFeature().Path; IEdmNavigationSource targetNavigationSource = path == null ? null : path.NavigationSource; // serialize a response string preferHeader = GetRequestPreferHeader(internalRequestHeaders); string annotationFilter = null; if (!String.IsNullOrEmpty(preferHeader)) { ODataMigrationMessageWrapper messageWrapper = getODataMessageWrapper(null); messageWrapper.SetHeader("Prefer", preferHeader); annotationFilter = messageWrapper.PreferHeader().AnnotationFilter; } ODataMigrationMessageWrapper responseMessageWrapper = getODataMessageWrapper(internalRequest.GetRequestContainer()); IODataResponseMessage responseMessage = responseMessageWrapper; if (annotationFilter != null) { responseMessage.PreferenceAppliedHeader().AnnotationFilter = annotationFilter; } ODataMessageWriterSettings writerSettings = internalRequest.GetWriterSettings(); writerSettings.BaseUri = baseAddress; writerSettings.Version = ODataVersion.V4; // Todo how to specify v3? Maybe don't because reading as v4 writerSettings.Validations = writerSettings.Validations & ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType; string metadataLink = internalUrlHelper.CreateODataLink(MetadataSegment.Instance); if (metadataLink == null) { throw new SerializationException("Unable to determine metadata url"); } writerSettings.ODataUri = new ODataUri { ServiceRoot = baseAddress, SelectAndExpand = internalRequest.ODataFeature()?.SelectExpandClause, Apply = internalRequest.ODataFeature().ApplyClause, Path = (path == null || IsOperationPath(path)) ? null : path.Path, }; ODataMetadataLevel metadataLevel = ODataMetadataLevel.MinimalMetadata; if (contentType != null) { IEnumerable <KeyValuePair <string, string> > parameters = contentType.Parameters.Select(val => new KeyValuePair <string, string>(val.Name.Value, val.Value.Value)); metadataLevel = ODataMediaTypes.GetMetadataLevel(contentType.MediaType.Value, parameters); } using (ODataMessageWriter messageWriter = new ODataMessageWriter(responseMessage, writerSettings, model)) { ODataSerializerContext writeContext = getODataSerializerContext(); writeContext.NavigationSource = targetNavigationSource; writeContext.Model = model; writeContext.RootElementName = GetRootElementName(path) ?? "root"; writeContext.SkipExpensiveAvailabilityChecks = serializer.ODataPayloadKind == ODataPayloadKind.ResourceSet; writeContext.Path = path; writeContext.SelectExpandClause = internalRequest.ODataFeature()?.SelectExpandClause; writeContext.MetadataLevel = metadataLevel; // Substitute stream to swap @odata.context Stream substituteStream = new MemoryStream(); Stream originalStream = messageWriter.SubstituteResponseStream(substituteStream); serializer.WriteObject(value, type, messageWriter, writeContext); StreamReader reader = new StreamReader(substituteStream); substituteStream.Seek(0, SeekOrigin.Begin); JToken responsePayload = JToken.Parse(reader.ReadToEnd()); // If odata context is present, replace with odata metadata if (responsePayload["@odata.context"] != null) { responsePayload["odata.metadata"] = responsePayload["@odata.context"].ToString().Replace("$entity", "@Element"); ((JObject)responsePayload).Property("@odata.context").Remove(); } // Write to actual stream // We cannot dispose of the stream because this method does not own the stream (subsequent methods will close the streamwriter) StreamWriter streamWriter = new StreamWriter(originalStream); JsonTextWriter writer = new JsonTextWriter(streamWriter); JsonSerializer jsonSerializer = new JsonSerializer(); jsonSerializer.Serialize(writer, responsePayload); writer.Flush(); messageWriter.SubstituteResponseStream(originalStream); } }