/// <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()));
            }
        }