/// <summary> /// Derived classes override this method to provide custom invocation behavior. /// </summary> /// <param name="instance">Instance to invoke the invoker against.</param> /// <param name="inputs">Input parameters post conversion.</param> /// <returns>Result of invocation.</returns> protected override async ValueTask <object> InvokeCoreAsync(object instance, object[] inputs) { ServiceInvokeResult invokeResult; try { InvokeDescription description = new InvokeDescription(this.operation, inputs); invokeResult = await((DomainService)instance).InvokeAsync(description, CancellationToken.None).ConfigureAwait(false); } catch (UnauthorizedAccessException ex) { throw new DomainDataServiceException((int)System.Net.HttpStatusCode.Unauthorized, ex.Message, ex); } catch (Exception ex) { if (ex.IsFatal()) { throw; } else { throw new DomainDataServiceException(Resource.DomainDataService_General_Error, ex); } } // This will throw if there are any validation erros DomainDataServiceException.HandleValidationErrors(invokeResult.ValidationErrors); return(invokeResult.Result); }
/// <summary> /// Derived classes override this method to provide custom invocation behavior. /// </summary> /// <param name="instance">Instance to invoke the invoker against.</param> /// <param name="inputs">Input parameters post conversion.</param> /// <param name="outputs">Optional out parameters.</param> /// <returns>Result of invocation.</returns> protected override object InvokeCore(object instance, object[] inputs, out object[] outputs) { outputs = ServiceUtils.EmptyObjectArray; IEnumerable <ValidationResult> validationErrors; object result; try { InvokeDescription description = new InvokeDescription(this.operation, inputs); result = ((DomainService)instance).Invoke(description, out validationErrors); } catch (UnauthorizedAccessException ex) { throw new DomainDataServiceException((int)System.Net.HttpStatusCode.Unauthorized, ex.Message, ex); } catch (Exception ex) { if (ex.IsFatal()) { throw; } else { throw new DomainDataServiceException(Resource.DomainDataService_General_Error, ex); } } DomainDataServiceException.HandleValidationErrors(validationErrors); return(result); }
/// <summary>Serializes the current exception description to the specified <paramref name="stream"/>.</summary> /// <param name="stream">Stream to write to.</param> internal void SerializeXmlErrorToStream(Stream stream) { Debug.Assert(stream != null, "stream != null"); using (XmlWriter writer = ServiceUtils.CreateXmlWriterAndWriteProcessingInstruction(stream, ErrorSerializer.defaultEncoding)) { Debug.Assert(writer != null, "writer != null"); writer.WriteStartElement(ServiceUtils.XmlErrorElementName, ServiceUtils.DataWebMetadataNamespace); string errorCode, message, messageLang; DomainDataServiceException dataException = ExtractErrorValues(this._exception, out errorCode, out message, out messageLang); writer.WriteStartElement(ServiceUtils.XmlErrorCodeElementName, ServiceUtils.DataWebMetadataNamespace); writer.WriteString(errorCode); writer.WriteEndElement(); // </code> writer.WriteStartElement(ServiceUtils.XmlErrorMessageElementName, ServiceUtils.DataWebMetadataNamespace); writer.WriteAttributeString( ServiceUtils.XmlNamespacePrefix, // prefix ServiceUtils.XmlLangAttributeName, // localName null, // ns messageLang); // value writer.WriteString(message); writer.WriteEndElement(); // </message> // Always assuming verbose errors. Exception exception = (dataException == null) ? this._exception : dataException.InnerException; SerializeXmlException(writer, exception); writer.WriteEndElement(); // </error> writer.Flush(); } }
/// <summary> /// Checks if the HTTP request is a GET request and throws otherwise. /// </summary> /// <param name="httpMethod">Request HTTP method.</param> private static void DisallowNonGetRequests(string httpMethod) { if (httpMethod != ServiceUtils.HttpGetMethodName) { DomainDataServiceException e = new DomainDataServiceException((int)HttpStatusCode.MethodNotAllowed, Resource.DomainDataService_ResourceSets_Metadata_OnlyAllowedGet); e.ResponseAllowHeader = ServiceUtils.HttpGetMethodName; throw e; } }
/// <summary> /// Creates a delegate used for serializing the exception to outgoing stream. /// </summary> /// <param name="exception">Exception to serialize.</param> /// <param name="statusCode">Http status code from <paramref name="exception"/>.</param> /// <returns>Delegate that serializes the <paramref name="exception"/>.</returns> private static Action <Stream> HandleException(Exception exception, out HttpStatusCode statusCode) { Debug.Assert(TypeUtils.IsCatchableExceptionType(exception), "WebUtil.IsCatchableExceptionType(exception)"); Debug.Assert(exception != null, "exception != null"); DomainDataServiceException e = exception as DomainDataServiceException; statusCode = e != null ? (HttpStatusCode)e.StatusCode : HttpStatusCode.InternalServerError; return(new ErrorSerializer(exception).SerializeXmlErrorToStream); }
/// <summary> /// Gets values describing the <paramref name='exception' /> if it's a DomainDataServiceException; /// defaults otherwise. /// </summary> /// <param name='exception'>Exception to extract value from.</param> /// <param name='errorCode'>Error code from the <paramref name='exception' />; blank if not available.</param> /// <param name='message'>Message from the <paramref name='exception' />; blank if not available.</param> /// <param name='messageLang'>Message language from the <paramref name='exception' />; current default if not available.</param> /// <returns>The cast DataServiceException; possibly null.</returns> private static DomainDataServiceException ExtractErrorValues(Exception exception, out string errorCode, out string message, out string messageLang) { DomainDataServiceException dataException = exception as DomainDataServiceException; if (dataException != null) { errorCode = dataException.ErrorCode ?? string.Empty; message = dataException.Message ?? string.Empty; messageLang = dataException.MessageLanguage ?? CultureInfo.CurrentCulture.Name; return(dataException); } else { errorCode = string.Empty; message = Resource.DomainDataService_General_Error; messageLang = CultureInfo.CurrentCulture.Name; return(null); } }
/// <summary> /// Enables the creation of a custom FaultException that is returned from an exception in the course of a service method. /// </summary> /// <param name="error">The Exception object thrown in the course of the service operation.</param> /// <param name="version">The SOAP version of the message.</param> /// <param name="fault">The Message object that is returned to the client, or service, in the duplex case.</param> public void ProvideFault(Exception error, MessageVersion version, ref Message fault) { Debug.Assert(error != null, "error != null"); Debug.Assert(version != null, "version != null"); HttpStatusCode statusCode; Action <Stream> exceptionWriter = DomainDataServiceErrorHandler.HandleException(error, out statusCode); Message message = null; try { message = Message.CreateMessage(MessageVersion.None, string.Empty, new DelegateBodyWriter(exceptionWriter)); message.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw)); HttpResponseMessageProperty response = new HttpResponseMessageProperty(); response.Headers[HttpResponseHeader.ContentType] = ServiceUtils.MimeApplicationXml; response.Headers[ServiceUtils.HttpDataServiceVersion] = ServiceUtils.DataServiceVersion1Dot0 + ";"; response.StatusCode = statusCode; if (statusCode == HttpStatusCode.MethodNotAllowed) { DomainDataServiceException e = (error as DomainDataServiceException); if (e != null) { response.Headers[HttpResponseHeader.Allow] = e.ResponseAllowHeader; } } message.Properties.Add(HttpResponseMessageProperty.Name, response); fault = message; message = null; } finally { if (message != null) { ((IDisposable)message).Dispose(); } } }
/// <summary> /// Derived classes override this method to provide custom invocation behavior. /// </summary> /// <param name="instance">Instance to invoke the invoker against.</param> /// <param name="inputs">Input parameters post conversion.</param> /// <param name="outputs">Optional out parameters.</param> /// <returns>Result of invocation.</returns> protected override object InvokeCore(object instance, object[] inputs, out object[] outputs) { outputs = ServiceUtils.EmptyObjectArray; // DEVNOTE(wbasheer): Need to perform query composition here for query options, potentially // need to inject the query options in the message properties somewhere. QueryDescription queryDesc = new QueryDescription(this.operation, inputs); IEnumerable <ValidationResult> validationErrors; int totalCount; IEnumerable <TEntity> result; try { result = (IEnumerable <TEntity>)((DomainService)instance).Query(queryDesc, out validationErrors, out totalCount); } catch (UnauthorizedAccessException ex) { throw new DomainDataServiceException((int)System.Net.HttpStatusCode.Unauthorized, ex.Message, ex); } catch (Exception ex) { if (ex.IsFatal()) { throw; } else { throw new DomainDataServiceException(Resource.DomainDataService_General_Error, ex); } } DomainDataServiceException.HandleValidationErrors(validationErrors); // DEVNOTE(wbasheer): Potentially return something that contains both the sequence and // the count value obtained from the query operation. return(result); }
/// <summary> /// Derived classes override this method to provide custom invocation behavior. /// </summary> /// <param name="instance">Instance to invoke the invoker against.</param> /// <param name="inputs">Input parameters post conversion.</param> /// <returns>Result of invocation.</returns> protected override async ValueTask <object> InvokeCoreAsync(object instance, object[] inputs) { // DEVNOTE(wbasheer): Need to perform query composition here for query options, potentially // need to inject the query options in the message properties somewhere. QueryDescription queryDesc = new QueryDescription(this.operation, inputs); IEnumerable <ValidationResult> validationErrors; IEnumerable <TEntity> result; try { var queryResult = await((DomainService)instance).QueryAsync <TEntity>(queryDesc, CancellationToken.None).ConfigureAwait(false); validationErrors = queryResult.ValidationErrors; result = (IEnumerable <TEntity>)queryResult.Result; } catch (UnauthorizedAccessException ex) { throw new DomainDataServiceException((int)System.Net.HttpStatusCode.Unauthorized, ex.Message, ex); } catch (Exception ex) { if (ex.IsFatal()) { throw; } else { throw new DomainDataServiceException(Resource.DomainDataService_General_Error, ex); } } DomainDataServiceException.HandleValidationErrors(validationErrors); // DEVNOTE(wbasheer): Potentially return something that contains both the sequence and // the count value obtained from the query operation. return(result); }
/// <summary> /// Selects the service operation to call. /// </summary> /// <param name="message">The Message object sent to invoke a service operation.</param> /// <param name="uriMatched">A value that specifies whether the URI matched a specific service operation.</param> /// <returns>The name of the service operation to call.</returns> protected override string SelectOperation(ref Message message, out bool uriMatched) { uriMatched = false; string[] segments = UriUtils.EnumerateSegments(message.Properties.Via, this.baseUri); Debug.Assert(segments != null, "We should be getting a non-null segments collection."); object property; HttpRequestMessageProperty httpRequestMessageProperty = null; if (message.Properties.TryGetValue(HttpRequestMessageProperty.Name, out property)) { httpRequestMessageProperty = (HttpRequestMessageProperty)property; } if (httpRequestMessageProperty == null) { return(base.SelectOperation(ref message, out uriMatched)); } // Dis-allow query options. DomainDataServiceOperationSelector.DisallowQueryOptions(message.Properties.Via); string identifier = null; if (segments.Length > 0 && UriUtils.ExtractSegmentIdentifier(segments[0], out identifier)) { // Disallow selection of entries within response sets. DomainDataServiceOperationSelector.DisallowEntrySelection(identifier, segments[0]); } // Service description or metadata request. if (0 == segments.Length || (1 == segments.Length && identifier == ServiceUtils.MetadataOperationName)) { DomainDataServiceOperationSelector.DisallowNonGetRequests(httpRequestMessageProperty.Method); // Metadata requests only available through GET. uriMatched = true; Collection <UriTemplateMatch> matches = DomainDataServiceOperationSelector.CreateAstoriaTemplate(this.baseUri).Match(message.Properties.Via); if (matches.Count > 0) { message.Properties.Add( ServiceUtils.UriTemplateMatchResultsPropertyName, matches[0]); // Dis-allow json requests for metadata documents. DomainDataServiceOperationSelector.DisallowJsonRequests( identifier != null ? RequestKind.MetadataDocument : RequestKind.ServiceDocument, httpRequestMessageProperty.Headers[HttpRequestHeader.Accept]); return(identifier ?? ServiceUtils.ServiceDocumentOperationName); } else { // Let the base error with Endpoint not found. return(base.SelectOperation(ref message, out uriMatched)); } } else { if (segments.Length > 1) { // More than 1 segments e.g. navigation is not supported. throw new DomainDataServiceException((int)HttpStatusCode.BadRequest, Resource.DomainDataService_MultipleSegments_NotAllowed); } // Remove the trailing parentheses from the URI. message.Headers.To = UriUtils.ReplaceLastSegment(message.Headers.To, identifier); string operationName; if (this.serviceRootQueryOperations.TryGetValue(identifier, out operationName)) { // Only allow GETs on resource sets. DomainDataServiceOperationSelector.DisallowNonGetRequests(httpRequestMessageProperty.Method); // Check if a resource set request matches current request. uriMatched = true; message.Properties.Add( ServiceUtils.UriTemplateMatchResultsPropertyName, DomainDataServiceOperationSelector.CreateAstoriaTemplate(this.baseUri).Match(message.Properties.Via)[0]); // Dis-allow json requests for resource sets. DomainDataServiceOperationSelector.DisallowJsonRequests(RequestKind.ResourceSet, httpRequestMessageProperty.Headers[HttpRequestHeader.Accept]); return(operationName); } } string result; try { // Delegate to base for all non-root query operations. result = base.SelectOperation(ref message, out uriMatched); } catch (Exception innerException) { if (innerException.IsFatal()) { throw; } else { throw new DomainDataServiceException((int)HttpStatusCode.NotFound, Resource.DomainDataService_Selection_Error, innerException); } } if (uriMatched == false) { throw new DomainDataServiceException((int)HttpStatusCode.NotFound, Resource.DomainDataService_Operation_NotFound); } else if (String.IsNullOrEmpty(result)) { DomainDataServiceException e = new DomainDataServiceException((int)HttpStatusCode.MethodNotAllowed, Resource.DomainDataService_Operation_Method_NotAllowed); e.ResponseAllowHeader = httpRequestMessageProperty.Method == ServiceUtils.HttpGetMethodName ? ServiceUtils.HttpPostMethodName : ServiceUtils.HttpGetMethodName; throw e; } // Dis-allow json returning service operation requests. DomainDataServiceOperationSelector.DisallowJsonRequests(RequestKind.ServiceOperation, httpRequestMessageProperty.Headers[HttpRequestHeader.Accept]); return(result); }
/// <summary> /// Selects the service operation to call. /// </summary> /// <param name="message">The Message object sent to invoke a service operation.</param> /// <param name="uriMatched">A value that specifies whether the URI matched a specific service operation.</param> /// <returns>The name of the service operation to call.</returns> protected override string SelectOperation(ref Message message, out bool uriMatched) { uriMatched = false; string[] segments = UriUtils.EnumerateSegments(message.Properties.Via, this.baseUri); Debug.Assert(segments != null, "We should be getting a non-null segments collection."); object property; HttpRequestMessageProperty httpRequestMessageProperty = null; if (message.Properties.TryGetValue(HttpRequestMessageProperty.Name, out property)) { httpRequestMessageProperty = (HttpRequestMessageProperty)property; } if (httpRequestMessageProperty == null) { return base.SelectOperation(ref message, out uriMatched); } // Dis-allow query options. DomainDataServiceOperationSelector.DisallowQueryOptions(message.Properties.Via); string identifier = null; if (segments.Length > 0) { if (UriUtils.ExtractSegmentIdentifier(segments[0], out identifier)) { // Disallow selection of entries within response sets. DomainDataServiceOperationSelector.DisallowEntrySelection(identifier, segments[0]); } } // Service description or metadata request. if (0 == segments.Length || (1 == segments.Length && identifier == ServiceUtils.MetadataOperationName)) { DomainDataServiceOperationSelector.DisallowNonGetRequests(httpRequestMessageProperty.Method); // Metadata requests only available through GET. uriMatched = true; Collection<UriTemplateMatch> matches = DomainDataServiceOperationSelector.CreateAstoriaTemplate(this.baseUri).Match(message.Properties.Via); if (matches.Count > 0) { message.Properties.Add( ServiceUtils.UriTemplateMatchResultsPropertyName, matches[0]); // Dis-allow json requests for metadata documents. DomainDataServiceOperationSelector.DisallowJsonRequests( identifier != null ? RequestKind.MetadataDocument : RequestKind.ServiceDocument, httpRequestMessageProperty.Headers[HttpRequestHeader.Accept]); return identifier ?? ServiceUtils.ServiceDocumentOperationName; } else { // Let the base error with Endpoint not found. return base.SelectOperation(ref message, out uriMatched); } } else { if (segments.Length > 1) { // More than 1 segments e.g. navigation is not supported. throw new DomainDataServiceException((int)HttpStatusCode.BadRequest, Resource.DomainDataService_MultipleSegments_NotAllowed); } // Remove the trailing parentheses from the URI. message.Headers.To = UriUtils.ReplaceLastSegment(message.Headers.To, identifier); string operationName; if (this.serviceRootQueryOperations.TryGetValue(identifier, out operationName)) { // Only allow GETs on resource sets. DomainDataServiceOperationSelector.DisallowNonGetRequests(httpRequestMessageProperty.Method); // Check if a resource set request matches current request. uriMatched = true; message.Properties.Add( ServiceUtils.UriTemplateMatchResultsPropertyName, DomainDataServiceOperationSelector.CreateAstoriaTemplate(this.baseUri).Match(message.Properties.Via)[0]); // Dis-allow json requests for resource sets. DomainDataServiceOperationSelector.DisallowJsonRequests(RequestKind.ResourceSet, httpRequestMessageProperty.Headers[HttpRequestHeader.Accept]); return operationName; } } string result; try { // Delegate to base for all non-root query operations. result = base.SelectOperation(ref message, out uriMatched); } catch (Exception innerException) { if (innerException.IsFatal()) { throw; } else { throw new DomainDataServiceException((int)HttpStatusCode.NotFound, Resource.DomainDataService_Selection_Error, innerException); } } if (uriMatched == false) { throw new DomainDataServiceException((int)HttpStatusCode.NotFound, Resource.DomainDataService_Operation_NotFound); } else if (String.IsNullOrEmpty(result)) { DomainDataServiceException e = new DomainDataServiceException((int)HttpStatusCode.MethodNotAllowed, Resource.DomainDataService_Operation_Method_NotAllowed); e.ResponseAllowHeader = httpRequestMessageProperty.Method == ServiceUtils.HttpGetMethodName ? ServiceUtils.HttpPostMethodName : ServiceUtils.HttpGetMethodName; throw e; } // Dis-allow json returning service operation requests. DomainDataServiceOperationSelector.DisallowJsonRequests(RequestKind.ServiceOperation, httpRequestMessageProperty.Headers[HttpRequestHeader.Accept]); return result; }
/// <summary> /// Checks if the HTTP request is a GET request and throws otherwise. /// </summary> /// <param name="httpMethod">Request HTTP method.</param> private static void DisallowNonGetRequests(string httpMethod) { if (httpMethod != ServiceUtils.HttpGetMethodName) { DomainDataServiceException e = new DomainDataServiceException((int)HttpStatusCode.MethodNotAllowed, Resource.DomainDataService_ResourceSets_Metadata_OnlyAllowedGet); e.ResponseAllowHeader = ServiceUtils.HttpGetMethodName; throw e; } }