/// <inheritdoc/> public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) { if (graph == null) { throw Error.ArgumentNull("graph"); } if (messageWriter == null) { throw Error.ArgumentNull("messageWriter"); } ODataError oDataError = graph as ODataError; if (oDataError == null) { HttpError httpError = graph as HttpError; if (httpError == null) { string message = Error.Format(SRResources.ErrorTypeMustBeODataErrorOrHttpError, graph.GetType().FullName); throw new SerializationException(message); } else { oDataError = httpError.CreateODataError(); } } bool includeDebugInformation = oDataError.InnerError != null; messageWriter.WriteError(oDataError, includeDebugInformation); }
/// <summary> /// Writes an ODataError with the given custom instance annotation to the test stream. /// </summary> private void WriteError(params KeyValuePair <string, ODataValue>[] annotations) { var writerSettings = new ODataMessageWriterSettings { DisableMessageStreamDisposal = true }; writerSettings.SetContentType(ODataFormat.Json); writerSettings.SetServiceDocumentUri(new Uri("http://example.com/")); IODataResponseMessage messageToWrite = new InMemoryMessage { StatusCode = 400, Stream = this.stream }; var error = new ODataError(); var instanceAnnotations = new Collection <ODataInstanceAnnotation>(); error.SetInstanceAnnotations(instanceAnnotations); foreach (var pair in annotations) { ODataInstanceAnnotation annotation = new ODataInstanceAnnotation(pair.Key, pair.Value); instanceAnnotations.Add(annotation); } using (var writer = new ODataMessageWriter(messageToWrite, writerSettings, this.model)) { writer.WriteError(error, false); } }
/// <inheritdoc/> public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) { if (graph == null) { throw Error.ArgumentNull("graph"); } if (messageWriter == null) { throw Error.ArgumentNull("messageWriter"); } ODataError oDataError = graph as ODataError; if (oDataError == null) { HttpError httpError = graph as HttpError; if (httpError == null) { string message = Error.Format(SRResources.ErrorTypeMustBeODataErrorOrHttpError, graph.GetType().FullName); throw new SerializationException(message); } else { oDataError = httpError.CreateODataError(); } } bool includeDebugInformation = oDataError.InnerError != null; messageWriter.WriteError(oDataError, includeDebugInformation); }
/// <summary> /// Writes the error with fallback logic for XML cases where the writer is in an error state and a new writer must be created. /// </summary> /// <param name="messageWriter">The message writer.</param> /// <param name="encoding">The encoding to use for the error if we have to fallback.</param> /// <param name="responseStream">The response stream to write to in the fallback case.</param> /// <param name="args">The args for the error.</param> /// <param name="error">The error to write.</param> /// <param name="messageWriterBuilder">MessageWriterBuilder to use if a new ODataMessageWriter needs to be constructed.</param> private static void WriteErrorWithFallbackForXml(ODataMessageWriter messageWriter, Encoding encoding, Stream responseStream, HandleExceptionArgs args, ODataError error, MessageWriterBuilder messageWriterBuilder) { Debug.Assert(args != null, "args != null"); #if DEBUG Debug.Assert(args.ProcessExceptionWasCalled, "ProcessException was not called by the time we tried to serialze this error message with ODataLib."); #endif if (messageWriter != null) { try { // If the XmlWriter inside the ODataMessageWriter had entered Error state, ODataMessageWriter.WriteError would throw an InvalidOperationException // when we try to write to it. Note that XmlWriter doesn't always throw an XmlException when it enters Error state. // The right thing to do is we don't write any more because at this point we don't know what's been written to the underlying // stream. However we still should flush the writer to make sure that all the content that was written but is sitting in the buffers actually appears // in the stream before writing the instream error. Otherwise the buffer will be flushed when disposing the writer later and we would end up with // either content written after the instream error (this would also result in having the Xml declaration in the middle of the payload - // [Astoria-ODataLib-Integration] In-stream errors due to XmlExceptions are written out backwards (error before partial valid payload)) or, // hypothetically, the instream error in the middle of the other content that was already partially written. For example we can end up with a payload that // looks like <element attr="val<m:error... The XmlReader would not be able to parse the error payload in this case. Disposing the writer will flush the buffer. // It is fine to do it since the writer is not usable at this point anyways. Also note that the writer will be disposed more than once (e.g. in finally block // in ResponseBodySerializer) but only the first call will have any effect. // However since in the versions we shipped we always create a new XmlWriter to serialize the error payload when the existing // one is in error state, we will continue to do the same to avoid introducing any breaking change here. messageWriter.WriteError(error, args.UseVerboseErrors); } catch (ODataException e) { // Yikes, ODataLib threw while writing the error. This tends to happen if the service author did something invalid during custom // error handling, such as add an custom instance annotation to the error payload. In this dire case, we treat it almost like // an in-stream error, and abort the previous writing. We write out the new error. Note that this will produce an invalid payload like // the situation noted above with XmlWriter errors. WebUtil.Dispose(messageWriter); messageWriterBuilder.SetMessageForErrorInError(); var newErrorWriter = messageWriterBuilder.CreateWriter(); ODataError errorWhileWritingOtherError = new ODataError() { ErrorCode = "500", InnerError = new ODataInnerError(e), Message = Strings.ErrorHandler_ErrorWhileWritingError }; newErrorWriter.WriteError(errorWhileWritingOtherError, args.UseVerboseErrors); } catch (InvalidOperationException) { Debug.Assert(ContentTypeUtil.IsNotJson(args.ResponseContentType), "Should never get here for JSON responses"); WebUtil.Dispose(messageWriter); // if either an InvalidOperationException was encountered (see comment above) or the message writer was null, write the error out manually. Debug.Assert(responseStream != null, "responseStream != null"); using (XmlWriter xmlWriter = XmlWriter.Create(responseStream, XmlUtil.CreateXmlWriterSettings(encoding))) { ErrorUtils.WriteXmlError(xmlWriter, error, args.UseVerboseErrors, MaxInnerErrorDepth); } } } }
/// <inheritdoc/> public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) { if (graph == null) { throw Error.ArgumentNull("graph"); } if (messageWriter == null) { throw Error.ArgumentNull("messageWriter"); } ODataError oDataError = GetError(graph); bool includeDebugInformation = oDataError.InnerError != null; messageWriter.WriteError(oDataError, includeDebugInformation); }
public void Write(MemoryStream stream, NephosErrorDetails errorDetails, Exception errorException, bool useVerboseErrors) { if (stream == null) { throw new ArgumentNullException("stream"); } if (errorDetails == null) { throw new ArgumentNullException("errorDetails"); } string item = null; string str = null; if (errorDetails.ResponseHeaders != null) { item = errorDetails.ResponseHeaders["Content-Type"]; str = errorDetails.ResponseHeaders["DataServiceVersion"]; } if (string.IsNullOrWhiteSpace(item)) { throw new ArgumentException(string.Format("errorDetails should contain a valid value for the key [{0}] in ResponseHeader.", "Content-Type")); } if (string.IsNullOrWhiteSpace(str)) { throw new ArgumentException(string.Format("errorDetails should contain a valid value for the key [{0}] in ResponseHeader.", "MaxDataServiceVersion")); } ODataError oDataError = new ODataError() { ErrorCode = errorDetails.StatusEntry.StatusId, Message = errorDetails.UserSafeErrorMessage, MessageLanguage = "en-US", InnerError = new ODataInnerError(errorException) }; ODataMessageWriterSettings oDataMessageWriterSetting = new ODataMessageWriterSettings() { DisableMessageStreamDisposal = true }; ResponseMessage responseMessage = new ResponseMessage(stream); responseMessage.SetHeader("MaxDataServiceVersion", str); responseMessage.SetHeader("Content-Type", item); using (ODataMessageWriter oDataMessageWriter = new ODataMessageWriter(responseMessage, oDataMessageWriterSetting)) { oDataMessageWriter.WriteError(oDataError, useVerboseErrors); } }
internal static void SerializeODataError(HandleExceptionArgs args, ODataMessageWriter writer, Stream outputStream, Encoding encoding) { ODataError error = CreateODataErrorFromExceptionArgs(args); try { writer.WriteError(error, args.UseVerboseErrors); } catch (InvalidOperationException) { if (!WebUtil.CompareMimeType(args.ResponseContentType, "application/json;odata=verbose")) { WebUtil.Dispose(writer); using (XmlWriter writer2 = XmlWriter.Create(outputStream, XmlUtil.CreateXmlWriterSettings(encoding))) { ErrorUtils.WriteXmlError(writer2, error, args.UseVerboseErrors, 100); } } } }
public void ShouldBeAbleToWriteCustomInstanceAnnotationToErrorInJsonLight() { const string expectedPayload = "{" + "\"error\":{" + "\"code\":\"400\"," + "\"message\":\"Resource not found for the segment 'Address'.\"," + "\"@instance.annotation\":\"stringValue\"" + "}" + "}"; var writerSettings = new ODataMessageWriterSettings { DisableMessageStreamDisposal = true }; writerSettings.SetContentType(ODataFormat.Json); writerSettings.ODataUri = new ODataUri() { ServiceRoot = new Uri("http://www.example.com") }; MemoryStream stream = new MemoryStream(); IODataResponseMessage messageToWrite = new InMemoryMessage { StatusCode = 400, Stream = stream }; // Write payload using (var messageWriter = new ODataMessageWriter(messageToWrite, writerSettings, Model)) { ODataError error = new ODataError { ErrorCode = "400", Message = "Resource not found for the segment 'Address'." }; error.InstanceAnnotations.Add(new ODataInstanceAnnotation("instance.annotation", new ODataPrimitiveValue("stringValue"))); messageWriter.WriteError(error, includeDebugInformation: true); } stream.Position = 0; string payload = (new StreamReader(stream)).ReadToEnd(); payload.Should().Be(expectedPayload); }
public override void WriteObject(object graph, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) { if (graph == null) { throw Error.ArgumentNull("graph"); } if (messageWriter == null) { throw Error.ArgumentNull("messageWriter"); } ODataError odataError = graph as ODataError; if (odataError == null) { throw Error.InvalidOperation(SRResources.ErrorTypeMustBeODataError, graph.GetType().FullName); } bool includeDebugInformation = odataError.InnerError != null; messageWriter.WriteError(odataError, includeDebugInformation); }
public override void WriteObject(object graph, ODataMessageWriter messageWriter, ODataSerializerWriteContext writeContext) { if (graph == null) { throw Error.ArgumentNull("graph"); } if (messageWriter == null) { throw Error.ArgumentNull("messageWriter"); } ODataError odataError = graph as ODataError; if (odataError == null) { throw Error.InvalidOperation(SRResources.ErrorTypeMustBeODataError, graph.GetType().FullName); } bool includeDebugInformation = odataError.InnerError != null; messageWriter.WriteError(odataError, includeDebugInformation); }
/// <summary> /// Writes an ODataError with the given custom instance annotation to the test stream. /// </summary> private void WriteError(params KeyValuePair<string, ODataValue>[] annotations) { var writerSettings = new ODataMessageWriterSettings { DisableMessageStreamDisposal = true }; writerSettings.SetContentType(ODataFormat.Json); writerSettings.SetServiceDocumentUri(new Uri("http://example.com/")); IODataResponseMessage messageToWrite = new InMemoryMessage { StatusCode = 400, Stream = this.stream }; var error = new ODataError(); var instanceAnnotations = new Collection<ODataInstanceAnnotation>(); error.SetInstanceAnnotations(instanceAnnotations); foreach (var pair in annotations) { ODataInstanceAnnotation annotation = new ODataInstanceAnnotation(pair.Key, pair.Value); instanceAnnotations.Add(annotation); } using (var writer = new ODataMessageWriter(messageToWrite, writerSettings, this.model)) { writer.WriteError(error, false); } }
internal static void SerializeODataError(HandleExceptionArgs args, ODataMessageWriter writer, Stream outputStream, Encoding encoding) { ODataError error = CreateODataErrorFromExceptionArgs(args); try { writer.WriteError(error, args.UseVerboseErrors); } catch (InvalidOperationException) { if (!WebUtil.CompareMimeType(args.ResponseContentType, "application/json;odata=verbose")) { WebUtil.Dispose(writer); using (XmlWriter writer2 = XmlWriter.Create(outputStream, XmlUtil.CreateXmlWriterSettings(encoding))) { ErrorUtils.WriteXmlError(writer2, error, args.UseVerboseErrors, 100); } } } }
public async Task WriteInStreamError_APIsShouldYieldSameResult() { var nestedWriterSettings = new ODataMessageWriterSettings { Version = ODataVersion.V4, EnableMessageStreamDisposal = false }; nestedWriterSettings.SetServiceDocumentUri(new Uri(ServiceUri)); var asyncException = await Assert.ThrowsAsync <ODataException>(async() => { IODataResponseMessage asyncResponseMessage = new InMemoryMessage { StatusCode = 200, Stream = this.asyncStream }; using (var messageWriter = new ODataMessageWriter(asyncResponseMessage, writerSettings)) { // Call to CreateODataAsynchronousWriterAsync triggers setting of output in-stream error listener var asynchronousWriter = await messageWriter.CreateODataAsynchronousWriterAsync(); var responseMessage = await asynchronousWriter.CreateResponseMessageAsync(); responseMessage.StatusCode = 200; // Next section added is to demonstrate that what was already written is flushed to the buffer before exception is thrown using (var nestedMessageWriter = new ODataMessageWriter(responseMessage, nestedWriterSettings)) { var writer = await nestedMessageWriter.CreateODataResourceWriterAsync(); } await messageWriter.WriteErrorAsync( new ODataError { ErrorCode = "NRE", Message = "Object reference not set to an instance of an object." }, /*includeDebugInformation*/ true); } }); this.asyncStream.Position = 0; var asyncResult = await new StreamReader(this.asyncStream).ReadToEndAsync(); var syncException = await Assert.ThrowsAsync <ODataException>( () => TaskUtils.GetTaskForSynchronousOperation(() => { IODataResponseMessage syncResponseMessage = new InMemoryMessage { StatusCode = 200, Stream = this.syncStream }; using (var messageWriter = new ODataMessageWriter(syncResponseMessage, writerSettings)) { // Call to CreateODataAsynchronousWriterAsync triggers setting of output in-stream error listener var asynchronousWriter = messageWriter.CreateODataAsynchronousWriter(); var responseMessage = asynchronousWriter.CreateResponseMessage(); responseMessage.StatusCode = 200; // Next section is added to demonstrate that what was already written is flushed to the buffer before exception is thrown using (var nestedMessageWriter = new ODataMessageWriter(responseMessage, nestedWriterSettings)) { var writer = nestedMessageWriter.CreateODataResourceWriter(); } messageWriter.WriteError( new ODataError { ErrorCode = "NRE", Message = "Object reference not set to an instance of an object." }, /*includeDebugInformation*/ true); } })); this.syncStream.Position = 0; var syncResult = await new StreamReader(this.syncStream).ReadToEndAsync(); var expected = @"HTTP/1.1 200 OK OData-Version: 4.0 Content-Type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8 "; Assert.Equal(Strings.ODataAsyncWriter_CannotWriteInStreamErrorForAsync, asyncException.Message); Assert.Equal(Strings.ODataAsyncWriter_CannotWriteInStreamErrorForAsync, syncException.Message); Assert.Equal(expected, asyncResult); Assert.Equal(expected, syncResult); }
/// <summary> /// Writes the error with fallback logic for XML cases where the writer is in an error state and a new writer must be created. /// </summary> /// <param name="messageWriter">The message writer.</param> /// <param name="encoding">The encoding to use for the error if we have to fallback.</param> /// <param name="responseStream">The response stream to write to in the fallback case.</param> /// <param name="args">The args for the error.</param> /// <param name="error">The error to write.</param> /// <param name="messageWriterBuilder">MessageWriterBuilder to use if a new ODataMessageWriter needs to be constructed.</param> private static void WriteErrorWithFallbackForXml(ODataMessageWriter messageWriter, Encoding encoding, Stream responseStream, HandleExceptionArgs args, ODataError error, MessageWriterBuilder messageWriterBuilder) { Debug.Assert(args != null, "args != null"); #if DEBUG Debug.Assert(args.ProcessExceptionWasCalled, "ProcessException was not called by the time we tried to serialze this error message with ODataLib."); #endif if (messageWriter != null) { try { // If the XmlWriter inside the ODataMessageWriter had entered Error state, ODataMessageWriter.WriteError would throw an InvalidOperationException // when we try to write to it. Note that XmlWriter doesn't always throw an XmlException when it enters Error state. // The right thing to do is we don't write any more because at this point we don't know what's been written to the underlying // stream. However we still should flush the writer to make sure that all the content that was written but is sitting in the buffers actually appears // in the stream before writing the instream error. Otherwise the buffer will be flushed when disposing the writer later and we would end up with // either content written after the instream error (this would also result in having the Xml declaration in the middle of the payload - // [Astoria-ODataLib-Integration] In-stream errors due to XmlExceptions are written out backwards (error before partial valid payload)) or, // hypothetically, the instream error in the middle of the other content that was already partially written. For example we can end up with a payload that // looks like <element attr="val<m:error... The XmlReader would not be able to parse the error payload in this case. Disposing the writer will flush the buffer. // It is fine to do it since the writer is not usable at this point anyways. Also note that the writer will be disposed more than once (e.g. in finally block // in ResponseBodySerializer) but only the first call will have any effect. // However since in the versions we shipped we always create a new XmlWriter to serialize the error payload when the existing // one is in error state, we will continue to do the same to avoid introducing any breaking change here. messageWriter.WriteError(error, args.UseVerboseErrors); } catch (ODataException e) { // Yikes, ODataLib threw while writing the error. This tends to happen if the service author did something invalid during custom // error handling, such as add an custom instance annotation to the error payload. In this dire case, we treat it almost like // an in-stream error, and abort the previous writing. We write out the new error. Note that this will produce an invalid payload like // the situation noted above with XmlWriter errors. WebUtil.Dispose(messageWriter); messageWriterBuilder.SetMessageForErrorInError(); var newErrorWriter = messageWriterBuilder.CreateWriter(); ODataError errorWhileWritingOtherError = new ODataError() { ErrorCode = "500", InnerError = new ODataInnerError(e), Message = Strings.ErrorHandler_ErrorWhileWritingError }; newErrorWriter.WriteError(errorWhileWritingOtherError, args.UseVerboseErrors); } catch (InvalidOperationException) { Debug.Assert(ContentTypeUtil.IsNotJson(args.ResponseContentType), "Should never get here for JSON responses"); WebUtil.Dispose(messageWriter); // if either an InvalidOperationException was encountered (see comment above) or the message writer was null, write the error out manually. Debug.Assert(responseStream != null, "responseStream != null"); using (XmlWriter xmlWriter = XmlWriter.Create(responseStream, XmlUtil.CreateXmlWriterSettings(encoding))) { ErrorUtils.WriteXmlError(xmlWriter, error, args.UseVerboseErrors, MaxInnerErrorDepth); } } } }
public void ShouldBeAbleToWriteCustomInstanceAnnotationToErrorInJsonLight() { const string expectedPayload = "{" + "\"error\":{" + "\"code\":\"400\"," + "\"message\":\"Resource not found for the segment 'Address'.\"," + "\"@instance.annotation\":\"stringValue\"" + "}" + "}"; var writerSettings = new ODataMessageWriterSettings { DisableMessageStreamDisposal = true }; writerSettings.SetContentType(ODataFormat.Json); writerSettings.ODataUri = new ODataUri() { ServiceRoot = new Uri("http://www.example.com") }; MemoryStream stream = new MemoryStream(); IODataResponseMessage messageToWrite = new InMemoryMessage { StatusCode = 400, Stream = stream }; // Write payload using (var messageWriter = new ODataMessageWriter(messageToWrite, writerSettings, Model)) { ODataError error = new ODataError { ErrorCode = "400", Message = "Resource not found for the segment 'Address'." }; error.InstanceAnnotations.Add(new ODataInstanceAnnotation("instance.annotation", new ODataPrimitiveValue("stringValue"))); messageWriter.WriteError(error, includeDebugInformation: true); } stream.Position = 0; string payload = (new StreamReader(stream)).ReadToEnd(); payload.Should().Be(expectedPayload); }