http-Header mit WCF-Behavior manipulieren

23.10.2018

Eine Kunde möchte an einem BasicHttp-Send-Port eine HTTP-Authentifizierung durchführen. Die Credentials sollen dabei über eine Pipeline-Komponente eingefügt werden. Der BasicHttp-Adapter unterstützt das aber nicht.

Die Lösung: Wir stecken den BasicHttp-Adapter als Binding in einen WCF-Adapter und schreiben für diesen ein Endpoint-Behavior.

Wir haben uns entschieden, dass das Behavior das Setzen von beliebigen HTTP-Headern erlauben soll.

Zuerst schreiben wir uns also einen IClientMessageInspector:

public class AddHttpHeaderInspector : IClientMessageInspector

{

Wir wollen ein Context-Property des WCF-Adapters setzen:

private const string HttpHeadersKey = "http://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties#HttpHeaders";

 

public void AfterReceiveReply(ref Message reply, object correlationState) {}

public object BeforeSendRequest(ref Message request, IClientChannel channel)

{

if (!request.Properties.ContainsKey(HttpHeadersKey)) { return null; }

Für den WCF-Adapter muss die SOAP-Action angegeben werden; für den BasicHttp-Adapter aber nicht. Um dieses Verhalten beizubehalten, löschen wir die SOAP-Action im Behavior. Der Nutzer muss aber noch irgendetwas angeben.

request.Headers.Action = null;

Hier setzen wir jetzt die Header. Wir erwarten, dass die Header im normalen HTTP-Format angegeben werden, d. h. Name von Inhalt durch Doppelpunkt getrennt und jeder Header auf einer eigenen Zeile.

var headers = ((string)request.Properties[HttpHeadersKey])

.Split('\n')

.Select(str => str.Split(':'))

.Where(header => header.Length == 2)

.Select(header => new { Name = header[0].Trim(), Content = header[1].Trim() });

Jetzt holen wir uns das HTTP-Request-Property aus der Message:

HttpRequestMessageProperty httpRequestMessage;

object httpRequestMessageObject;

if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))

{

httpRequestMessage = (HttpRequestMessageProperty)httpRequestMessageObject;

}

else

{

httpRequestMessage = new HttpRequestMessageProperty();

request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);

}

Anschließend setzen wir die Header, die in einem Dictionary übergeben werden:

foreach (var header in headers)

{

httpRequestMessage.Headers[header.Name] = header.Content;

}

return null;

}

}

}

Zudem benötigen wir zwei eher triviale Klassen, um das Behavior zu vervollständigen:

public class AddHttpHeaderBehavior : IEndpointBehavior

{

public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)

{

AddHttpHeaderInspector headerInspector = new AddHttpHeaderInspector();

clientRuntime.MessageInspectors.Add(headerInspector);

}

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }

public void Validate(ServiceEndpoint endpoint) { }

}

 

public class AddHttpHeaderBehaviorExtensionElement : BehaviorExtensionElement

{

protected override object CreateBehavior()

{

return new AddHttpHeaderBehavior();

}

 

public override Type BehaviorType

{

get { return typeof(AddHttpHeaderBehavior); }

}

}

 

Als nächstes benötigen wir die PipelineKomponente. Diese könnte zum Beispiel so aussehen:

[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]

[ComponentCategory(CategoryTypes.CATID_Encoder)]

[Guid("88CDCE17-D9B1-47BA-98C6-3BF7E9E2FE65")]

public class HttpHeaderComponent : IBaseComponent, IComponent, IComponentUI

{

public string Description => "HttpHeaderComponent";

public string Name => "HttpHeaderComponent";

public string Version => "1.0.0";

public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)

{

pInMsg.Context.Write(

"HttpHeaders",

"http://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties",

"Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\nAccept-Encoding: gzip, deflate");

return pInMsg;

}

public IntPtr Icon => IntPtr.Zero;

public System.Collections.IEnumerator Validate(object projectSystem) => new string[0].GetEnumerator();

}

Der Großteil der Klasse kann beliebig ausgestaltet werden. In Execute setzen wir das Authorisierungsschema auf Basic mit Benutzer „Aladdin“ und Password „OpenSesame“; außerdem akzeptieren wir gzip- und DEFLATE-komprimierte Daten.

Nachdem wir diese Projekte kompiliert haben, installieren wir die Pipeline ganz normal als BizTalk-Assembly. Das Behavior muss in den Global Assembly Cache installiert werden, z. B. mit gacutil oder BizTalk. Dann muss in der machine.config innerhalb des behaviorExtensions-Tags eine Zeile analog zu

<add name="biztalkAddHttpHeader" type="WCFBasicHttpAdditionalHeaders.AddHttpHeaderBehaviorExtensionElement, WCFBasicHttpAdditionalHeaders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9350da1595971f93"/>

eingefügt werden. Die Datei machine.config findet sich jeweils in C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\ und C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\. Es empfiehlt sich, jetzt die entsprechenden Host-Instanzen neuzustarten.

Das Behavior ist nun einsatzbereit. Wir erstellen einen neuen Send Port vom Typ WCF-Custom und setzen dort unsere Pipeline ein.

Wir setzen die URL auf den gewünschten Endpunkt ein und tragen bei Action etwas Beliebiges ein; dieser Wert wird später vom Behavior wieder gelöscht, aber er sorgt dafür, dass der Port funktioniert. Unter Binding setzen wir den Binding Type auf „basicHttpBinding“ und unter Security den Mode auf „Transport“.


 

Zuletzt fügen wir unter Behavior noch unser Behavior hinzu.

Damit ist der Send Port fertig und meldet sich nun am Endpunkt mit dem Benutzer „Aladdin“ und dem Passwort „OpenSesame“ an.

Dieser Tech-Tipp kommt von QUIBIQ Berlin.

 

zurück