Im Augenblick entwickel ich eine Architektur für eine WEB-API basierend auf der WCF. Als Grundlage habe ich das
WCF Http Projekt (das auf Codeplex zu finde ist) genommen. Mir gefällt dort insbesondere der Ansatz der
MediaTypeProcessor, womit sich die Request- und Response-Formate sehr schön beeinflussen lassen.
Ein wichtier Aspekt bei jeder Architektur ist das Thema Exceptionhandling. Leider gibt es für das oben genannte Projekt noch keinen eigenen Exceptionhandler, der auch die
MediaTypeProcessors nutzt, um eine Exception im angeforderten Format zurück zu geben. Das kann z.B.
XML,
Json aber theoretisch auch ein Bild oder Wav Dateien sein.
Daher habe ich heute mal einen Exceptionhandler geschrieben, der mit dem Projekt zusammenarbeitet. Als Basis wird hierbei natürlich das Interface der WCF
IErrorHandler genutzt und eine generelle Basisimplementierung aus dem Projekt.
Ich bin noch nicht 100% glücklich mit der Lösung aber im Augenblick funktioniert das so ganz gut. Werde die Implementierung auch auf Codeplex posten und hoffe dort vielleicht weitern Input zu finden. Aber auch per E-Mail freue ich mich über konstruktive Beiträge!
Here we go:
public class RESTMessageErrorHandler : HttpMessageErrorHandler, IErrorHandler
{
// Public Methods
#region HandleError
public override bool HandleError(
Exception error)
{
Logging.Error("API Exception", error);
return true;
}
#endregion
// Protected Methods
#region ProvideResponse
protected override void ProvideResponse(
Exception exception,
Microsoft.Http.HttpResponseMessage response)
{
APIBaseException apiException = null;
if (exception is APIBaseException)
apiException = exception as APIBaseException;
else
apiException = new APIBaseException(System.Net.HttpStatusCode.InternalServerError,
"An error has occured processing your request.");
var supportedMediaTypes = new List<string>();
var httpMessageProperty = OperationContext.Current.
IncomingMessageProperties[HttpMessageProperty.Name]
as HttpMessageProperty;
var httpRequest = httpMessageProperty.Request as HttpRequestMessage;
var contentType = httpRequest.Headers.ContentType;
var uriMatch = httpRequest.Properties.First(
p => p.GetType() == typeof(UriTemplateMatch)) as UriTemplateMatch;
var endpoint = OperationContext.Current.Host.Description.Endpoints.Find(
OperationContext.Current.EndpointDispatcher.EndpointAddress.Uri);
var dispatchOperation = OperationContext.Current.EndpointDispatcher.DispatchRuntime.
Operations.Where(op => op.Name == uriMatch.Data).First();
var operationDescription = endpoint.Contract.Operations.Find(dispatchOperation.Name);
//get the contenttype of the request
var httpBehavoir = endpoint.Behaviors.Find<HttpEndpointBehavior>();
var processors = httpBehavoir.GetResponseProcessors(
operationDescription.ToHttpOperationDescription()).ToList<Processor>();
//Fallback for empty contenttype
if (string.IsNullOrEmpty(contentType))
{
foreach (var processor in processors)
{
var mediaTypeProcessor = processor as MediaTypeProcessor;
if (mediaTypeProcessor == null)
continue;
supportedMediaTypes.AddRange(mediaTypeProcessor.SupportedMediaTypes);
}
contentType = ContentNegotiationHelper.GetBestMatch(httpRequest.Headers.Accept.ToString(),
supportedMediaTypes).MediaType;
if (string.IsNullOrEmpty(contentType))
contentType = "text/plain";
}
//set http-header and status code
response.Headers.ContentType = contentType;
response.StatusCode = apiException.Status;
//search processor for the output-serialization
foreach (var processor in processors)
{
var mediaTypeProcessor = processor as MediaTypeProcessor;
if (mediaTypeProcessor == null)
continue;
if (mediaTypeProcessor.SupportedMediaTypes.Contains<string>(contentType))
{
response.Content = HttpContent.Create(s => mediaTypeProcessor.
WriteToStream(new APIExceptionContract(apiException), s, httpRequest));
break;
}
}
//if no processor found use plain text
if (response.Content == null)
response.Content = HttpContent.Create(apiException.Description);
}
#endregion
}