// 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.Diagnostics.CodeAnalysis; using System.Linq; using System.Web.Http.ModelBinding; using System.Web.Http.Properties; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; 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. /// [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "This type is only a dictionary to get the right serialization format")] [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "DCS does not support IXmlSerializable types that are also marked as [Serializable]")] [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 Error.ArgumentNull("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 Error.ArgumentNull("exception"); } Message = SRResources.ErrorOccurred; 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 Error.ArgumentNull("modelState"); } if (modelState.IsValid) { throw Error.Argument("modelState", SRResources.ValidModelState); } Message = SRResources.BadRequest; HttpError modelStateError = new HttpError(); foreach (KeyValuePair keyModelStatePair in modelState) { string key = keyModelStatePair.Key; ModelErrorCollection errors = keyModelStatePair.Value.Errors; if (errors != null && errors.Count > 0) { IEnumerable errorMessages = errors.Select(error => { if (includeErrorDetail && error.Exception != null) { return error.Exception.Message; } else { return String.IsNullOrEmpty(error.ErrorMessage) ? SRResources.ErrorOccurred : error.ErrorMessage; } }).ToArray(); modelStateError.Add(key, errorMessages); } } Add(HttpErrorKeys.ModelStateKey, modelStateError); } /// /// Initializes a new instance of the class containing error message /// and error message detail . /// /// The error message to associate with this instance. /// The error message detail to associate with this instance. internal HttpError(string message, string messageDetail) : this(message) { if (messageDetail == null) { throw Error.ArgumentNull("message"); } Add(HttpErrorKeys.MessageDetailKey, messageDetail); } /// /// 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 . /// 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 . 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 . 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 . 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 . 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 . 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 . 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) { TValue value; if (this.TryGetValue(key, out value)) { return 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) { string key = XmlConvert.DecodeName(reader.LocalName); string value = reader.ReadInnerXml(); this.Add(key, value); reader.MoveToContent(); } reader.ReadEndElement(); } void IXmlSerializable.WriteXml(XmlWriter writer) { foreach (KeyValuePair keyValuePair in this) { string key = keyValuePair.Key; object value = keyValuePair.Value; writer.WriteStartElement(XmlConvert.EncodeLocalName(key)); if (value != null) { HttpError innerError = value as HttpError; if (innerError == null) { writer.WriteValue(value); } else { ((IXmlSerializable)innerError).WriteXml(writer); } } writer.WriteEndElement(); } } } }