// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.Linq; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; using Microsoft.AspNetCore.Mvc.ModelBinding; using ShimResources = Microsoft.AspNetCore.Mvc.WebApiCompatShim.Resources; namespace System.Web.Http { /// /// Defines a serializable container for storing error information. This information is stored /// as key/value pairs. The dictionary keys to look up standard error information are available /// on the type. /// [XmlRoot("Error")] public sealed class HttpError : Dictionary, IXmlSerializable { /// /// Initializes a new instance of the class. /// public HttpError() : base(StringComparer.OrdinalIgnoreCase) { } /// /// Initializes a new instance of the class containing error message /// . /// /// The error message to associate with this instance. public HttpError(string message) : this() { if (message == null) { throw new ArgumentNullException(nameof(message)); } Message = message; } /// /// Initializes a new instance of the class for . /// /// The exception to use for error information. /// /// true to include the exception information in the error;false otherwise. /// public HttpError(Exception exception, bool includeErrorDetail) : this() { if (exception == null) { throw new ArgumentNullException(nameof(exception)); } Message = ShimResources.HttpError_GenericError; if (includeErrorDetail) { Add(HttpErrorKeys.ExceptionMessageKey, exception.Message); Add(HttpErrorKeys.ExceptionTypeKey, exception.GetType().FullName); Add(HttpErrorKeys.StackTraceKey, exception.StackTrace); if (exception.InnerException != null) { Add(HttpErrorKeys.InnerExceptionKey, new HttpError(exception.InnerException, includeErrorDetail)); } } } /// /// Initializes a new instance of the class for . /// /// The invalid model state to use for error information. /// /// true to include exception messages in the error; false otherwise. /// public HttpError(ModelStateDictionary modelState, bool includeErrorDetail) : this() { if (modelState == null) { throw new ArgumentNullException(nameof(modelState)); } if (modelState.IsValid) { throw new ArgumentException(ShimResources.HttpError_ValidModelState, nameof(modelState)); } Message = ShimResources.HttpError_BadRequest; var modelStateError = new HttpError(); foreach (KeyValuePair keyModelStatePair in modelState) { var key = keyModelStatePair.Key; var errors = keyModelStatePair.Value.Errors; if (errors != null && errors.Count > 0) { var errorMessages = errors.Select(error => { if (includeErrorDetail && error.Exception != null) { return error.Exception.Message; } else { return string.IsNullOrEmpty(error.ErrorMessage) ? ShimResources.HttpError_GenericError : error.ErrorMessage; } }).ToArray(); modelStateError.Add(key, errorMessages); } } Add(HttpErrorKeys.ModelStateKey, modelStateError); } /// /// The high-level, user-visible message explaining the cause of the error. Information carried in this field /// should be considered public in that it will go over the wire regardless of the value of error detail /// policy. As a result care should be taken not to disclose sensitive information about the server or the /// application. /// public string Message { get { return GetPropertyValue(HttpErrorKeys.MessageKey); } set { this[HttpErrorKeys.MessageKey] = value; } } /// /// The containing information about the errors that occurred during model binding. /// /// /// The inclusion of information carried in the is /// controlled by the error detail policy. All other information in the /// should be considered public in that it will go over the wire. As a result care should be taken not to /// disclose sensitive information about the server or the application. /// public HttpError ModelState { get { return GetPropertyValue(HttpErrorKeys.ModelStateKey); } } /// /// A detailed description of the error intended for the developer to understand exactly what failed. /// /// /// The inclusion of this field is controlled by the error detail policy. The /// field is expected to contain information about the server or the application that should not /// be disclosed broadly. /// public string MessageDetail { get { return GetPropertyValue(HttpErrorKeys.MessageDetailKey); } set { this[HttpErrorKeys.MessageDetailKey] = value; } } /// /// The message of the if available. /// /// /// The inclusion of this field is controlled by the error detail policy. The /// field is expected to contain information about the server or the application that should not /// be disclosed broadly. /// public string ExceptionMessage { get { return GetPropertyValue(HttpErrorKeys.ExceptionMessageKey); } set { this[HttpErrorKeys.ExceptionMessageKey] = value; } } /// /// The type of the if available. /// /// /// The inclusion of this field is controlled by the error detail policy. The /// field is expected to contain information about the server or the application that should not /// be disclosed broadly. /// public string ExceptionType { get { return GetPropertyValue(HttpErrorKeys.ExceptionTypeKey); } set { this[HttpErrorKeys.ExceptionTypeKey] = value; } } /// /// The stack trace information associated with this instance if available. /// /// /// The inclusion of this field is controlled by the error detail policy. The /// field is expected to contain information about the server or the application that should not /// be disclosed broadly. /// public string StackTrace { get { return GetPropertyValue(HttpErrorKeys.StackTraceKey); } set { this[HttpErrorKeys.StackTraceKey] = value; } } /// /// The inner associated with this instance if available. /// /// /// The inclusion of this field is controlled by the error detail policy. The /// field is expected to contain information about the server or the application that should not /// be disclosed broadly. /// public HttpError InnerException { get { return GetPropertyValue(HttpErrorKeys.InnerExceptionKey); } } /// /// Gets a particular property value from this error instance. /// /// The type of the property. /// The name of the error property. /// The value of the error property. public TValue GetPropertyValue(string key) { object value; if (TryGetValue(key, out value) && value is TValue) { return (TValue)value; } return default(TValue); } XmlSchema IXmlSerializable.GetSchema() { return null; } void IXmlSerializable.ReadXml(XmlReader reader) { if (reader.IsEmptyElement) { reader.Read(); return; } reader.ReadStartElement(); while (reader.NodeType != System.Xml.XmlNodeType.EndElement) { var key = XmlConvert.DecodeName(reader.LocalName); var value = reader.ReadInnerXml(); Add(key, value); reader.MoveToContent(); } reader.ReadEndElement(); } void IXmlSerializable.WriteXml(XmlWriter writer) { foreach (var keyValuePair in this) { var key = keyValuePair.Key; var value = keyValuePair.Value; writer.WriteStartElement(XmlConvert.EncodeLocalName(key)); if (value != null) { var innerError = value as HttpError; if (innerError == null) { writer.WriteValue(value); } else { ((IXmlSerializable)innerError).WriteXml(writer); } } writer.WriteEndElement(); } } } }