Ich habe IErrorHandler implementiert, um Autorisierungsausnahmen zu verarbeiten, die im Konstruktor meines WCF-Diensts ausgelöst wurden. Wenn eine allgemeine Ausnahme abgefangen wird, wird mein benutzerdefinierter Typ wie erwartet zurückgegeben, aber der ContentType-Header ist falsch.IErrorHandler gibt falschen Nachrichtentext zurück, wenn der HTTP-Statuscode 401 lautet Nicht autorisiert
HTTP/1.1 500 Internal Server Error
Content-Type: application/xml;
...
{"ErrorMessage":"Error!"}
jedoch, wenn die Fehler-Handler versucht, einen 401 Unauthorized HTTP-Statuscode der Nachrichtentext auf den Standardtyp außer Kraft gesetzt wird, zurückzukehren, aber der Contenttype-Header ist wie es sein sollte.
HTTP/1.1 401 Unauthorized
Content-Type: application/json;
...
{"Message":"Authentication failed.","StackTrace":null,"ExceptionType":"System.InvalidOperationException"}
Offensichtlich stimmt hier etwas nicht, aber ich bin nicht sicher was.
Wie implementiere ich IErrorHandler so, dass es meinen benutzerdefinierten Typ in JSON mit den richtigen Headern zurückgibt?
BaseDataResponseContract Objekt:
[Serializable]
[DataContract(Name = "BaseDataResponseContract")]
public class BaseDataResponseContract
{
[DataMember]
public string ErrorMessage { get; set; }
} // end
Dies ist das Objekt, das ich zurückkehren möchten. Alle anderen Objekte in meiner Anwendung erben von diesem Objekt. Wenn eine Ausnahme ausgelöst wird, interessiert uns nur der HTTP-Statuscode und die Fehlermeldung.
IErrorHandler Implementation (Protokollierung der Kürze halber nicht dargestellt):
namespace WebServices.BehaviorsAndInspectors
{
public class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
} // end
public void ProvideFault(Exception ex, MessageVersion version, ref Message fault)
{
// Create a new instance of the object I would like to return with a default message
var baseDataResponseContract = new BaseDataResponseContract { ErrorMessage = "Error!" };
// Get the outgoing response portion of the current context
var response = WebOperationContext.Current.OutgoingResponse;
// Set the http status code
response.StatusCode = HttpStatusCode.InternalServerError;
// If the exception is a specific type change the default settings
if (ex.GetType() == typeof(UserNotFoundException))
{
baseDataResponseContract.ErrorMessage = "Invalid Username!";
response.StatusCode = HttpStatusCode.Unauthorized;
}
// Create the fault message that is returned (note the ref parameter)
fault = Message.CreateMessage(version, "", baseDataResponseContract, new DataContractJsonSerializer(typeof(BaseDataResponseContract)));
// Tell WCF to use JSON encoding rather than default XML
var webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, webBodyFormatMessageProperty);
// Add ContentType header that specifies we are using json
var httpResponseMessageProperty = new HttpResponseMessageProperty();
httpResponseMessageProperty.Headers[HttpResponseHeader.ContentType] = "application/json";
fault.Properties.Add(HttpResponseMessageProperty.Name, httpResponseMessageProperty);
} // end
} // end class
} // end namespace
IServiceBehavior Umsetzung:
namespace WebServices.BehaviorsAndInspectors
{
public class ErrorHandlerExtensionBehavior : BehaviorExtensionElement, IServiceBehavior
{
public override Type BehaviorType
{
get { return GetType(); }
}
protected override object CreateBehavior()
{
return this;
}
private IErrorHandler GetInstance()
{
return new ErrorHandler();
}
void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } // end
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
var errorHandlerInstance = GetInstance();
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
dispatcher.ErrorHandlers.Add(errorHandlerInstance);
}
}
void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } // end
} // end class
} // end namespace
Web.Config:
<system.serviceModel>
<services>
<service name="WebServices.MyService">
<endpoint binding="webHttpBinding" contract="WebServices.IMyService" />
</service>
</services>
<extensions>
<behaviorExtensions>
<!-- This extension if for the WCF Error Handling-->
<add name="ErrorHandlerBehavior" type="WebServices.BehaviorsAndInspectors.ErrorHandlerExtensionBehavior, WebServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<ErrorHandlerBehavior />
</behavior>
</serviceBehaviors>
</behaviors>
....
</system.serviceModel>
Schließlich sehe ich ein ähnliches Verhalten bei Verwendung von WebFaultException. Mein Gedanke ist, dass dies das Ergebnis einiger tief vergrabener .Net-Spielereien ist. Ich entscheide mich dafür, IErrorHandler zu implementieren, damit ich alle anderen Ausnahmen abfangen kann, die nicht behandelt werden können.
Referenz:
https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.100).aspx
Andere Beispiele:
IErrorHandler doesn't seem to be handling my errors in WCF .. any ideas?
How to make custom WCF error handler return JSON response with non-OK http code?
How do you set the Content-Type header for an HttpClient request?
Ja ... aber wie? Ich habe den gleichen progrem mit einem WebOperationContext.current.OutgoingResponse, aber ich habe keine Schreibmethode darin, so wie man eine benutzerdefinierte Antwort macht? – DestyNova