Skip to content

Add custom http header to the request #3472

Closed

Description

@CurlyBraceTT

How can I add custom http header to the wcf request in asp.net core? Maybe every wcf requests even?

I am trying to follow https://blogs.msmvps.com/paulomorgado/2007/04/26/wcf-building-an-http-user-agent-message-inspector/ but there is no BehaviorExtensionElement class and I assume configuration section will not be used.

Also tried this with no luck:

public System.Threading.Tasks.Task<Calculator.TestData> GetTestDataAsync()
{
	using (OperationContextScope scope = new OperationContextScope(base.InnerChannel))
	{
		OperationContext.Current?.OutgoingMessageProperties.Add("xd", "xd");
		return base.Channel.GetTestDataAsync();
	}
}

Activity

  1. Zek99 commented on Mar 18, 2019

    @Zek99

    Hi, I had a similar issue with forcing specific settings for requests. The article you are reading is from 2007 and is for the .NET framework rather than .NET Core.

    If you have a look at my issue #3442 you can see how I alter the requests.

    var client = new GeneratedSvcUtilClient();
    client.Endpoint.EndpointBehaviors.Add(new ForceCertificateEndpointBehavior());
    var channel = client.ChannelFactory.CreateChannel
    
    public class ForceCertificateEndpointBehavior : IEndpointBehavior
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
            var x5092 = new X509Certificate2("Settings for SHA1 signed certificate");
    
            bindingParameters.Add(new Func<HttpClientHandler, HttpMessageHandler>(x =>
            {
                x.SslProtocols = SslProtocols.Tls12;
                x.ClientCertificates.Add(x5092);
    
                return new CustomMessageHandler(x);
            }));
        }
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
        public void Validate(ServiceEndpoint endpoint) { }
    }
    
  2. CurlyBraceTT commented on Mar 19, 2019

    @CurlyBraceTT
    Author

    @Zek99 Big thanks!

    However, are you sure, that the custom handler are gonna be executed? For me binding parameter scope function are not being executed at all. Can you share the complete example?

    Here is mine code and full projects (Service & Client)
    https://drive.google.com/open?id=1Rt0xz-3ZFZ4WMKVmHadlG9Vi4pT5OdB5

    public class CustomCorrelationDelegatingHandler : DelegatingHandler
    {
    	public CustomCorrelationDelegatingHandler(HttpClientHandler handler)
    	{
    		InnerHandler = handler;
    	}
    
    	protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    	{
    		request.Headers.Add("xd", "xd");
    		return base.SendAsync(request, cancellationToken);
    	}
    }
    
    public class CorrelationEndpointBehavior : IEndpointBehavior
    {
    	public CorrelationEndpointBehavior()
    	{
    	}
    
    	public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    	{
    		bindingParameters.Add(new Func<HttpClientHandler, HttpMessageHandler>(x =>
    		{
    			return new CustomCorrelationDelegatingHandler(x);
    		}));
    	}
    
    	public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
    	public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
    	public void Validate(ServiceEndpoint endpoint) { }
    }
    
    var client = new CalculatorClient();
    client.Endpoint.EndpointBehaviors.Add(new CorrelationEndpointBehavior());
    
    var data = client.GetTestDataAsync().GetAwaiter().GetResult();
    
  3. Lxiamail commented on Mar 20, 2019

    @Lxiamail
    Contributor

    @CurlyBraceTT @Zek99 Please check out the sample code in this test

  4. added this to the milestone on Mar 20, 2019
  5. Zek99 commented on Mar 20, 2019

    @Zek99

    @CurlyBraceTT your CustomCorrelationDelegatingHandler should work. Confirmed using Telerik's Fiddler

    Using both header properties.

    request.Headers.Add("xd", "xd");
    request.Content.Headers.Add("x", "x");
    

    image

  6. bdrupieski commented on Mar 25, 2019

    @bdrupieski

    @CurlyBraceTT You need to create a HttpRequestMessageProperty and use that, like this:

    var httpRequestProperty = new HttpRequestMessageProperty();
    httpRequestProperty.Headers["xd"] = "xd";
    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
    

    Another potential issue is that OperationContextScope doesn't work well in async code. See this issue #2512. I've been using the solution from here successfully https://stackoverflow.com/questions/18284998/pattern-for-calling-wcf-service-using-async-await/22753055#22753055.

    The usage might look like something like this, where you use FlowingOperationContextScope instead of OperationContextScope, and call ContinueOnScope for every async call in there:

    public static async Task<T> WithPreemptiveBasicAuth<TClientInterface, T>(this ClientBase<TClientInterface> client, Func<Task<T>> taskFactory)
        where TClientInterface : class
    {
        using (var scope = new FlowingOperationContextScope(client.InnerChannel))
        {
            var username = client.ClientCredentials.UserName.UserName;
            var password = client.ClientCredentials.UserName.Password;
            var headerValue = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}"));
    
            var httpRequestProperty = new HttpRequestMessageProperty();
            httpRequestProperty.Headers[HttpRequestHeader.Authorization] = $"Basic {headerValue}";
            OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
    
            return await taskFactory().ContinueOnScope(scope);
        }
    }
    
  7. CurlyBraceTT commented on Mar 25, 2019

    @CurlyBraceTT
    Author

    @Zek99 Solved it - didn't work because of the outdated versions in the project - Visual Studio adds them outdated when generating the client. Works with the last 4.5.3 version of the System.ServiceModel.* packages.

    image

    @bdrupieski For now EndpointBehavior works for me and seems easier to setup.

  8. r-pankevicius commented on Mar 29, 2019

    @r-pankevicius

    Thank you guys, that my findings helped you to resolve the issue. Anyone can help me with "final solution" for WCF+Oauth2 in scope of https://github.com/r-pankevicius/WcfClientWithOMiauAuthentification?

    The issue is that SlimServiceClientInterceptor.Intercept method is sync while it must be async.

  9. mconnew commented on Mar 29, 2019

    @mconnew
    Member

    @r-pankevicius, I had a quick look at your project. I have an alternative suggestion for you. You could add the headers via a message inspector so you don't need to create an OperationContext scope. Also looking at your code, I don't think you need to make your Intercept method async if you add the constraint that the WCF contract interface only uses Task based async operations. You will need to do quite a bit more reflection code. At the end of your Intercept method, you need to set invocation.ReturnValue to the correct result. Here's where Task's help you. You don't need to actually return the "real" Task result, you just need to return a Task which completes with the same final result as the intercepted method does. So for example, if the return type from an method is Task, then invocation.ReturnValue only needs to be set to a Task which will complete with the right Foo object. It doesn't need to be the original Task instance.
    So what you need to do is create a Task InterceptAsync(IInvocation invocation) method which doesn't set invocation.ReturnValue, but instead returns a Task (which is the base type of Task so you don't need to worry about the concrete result type from the sync Intercept method). InterceptAsync is not and async method, but instantiates a TaskCompletionSource. This is where the extra reflection comes in, you need to use reflection to find out what TResult is, then create an instance of TaskCompletionSource for that type. You then have another method async void InterceptAsyncCore(IInvocation invocation, object tcs). This is async void as you won't be awaiting the call.From InterceptAsync, you call InterceptAsyncCore and then return TaskCompletionSource.Task to the Intercept method. The second parameter is an object as TaskCompletionSource is generic and you don't have the generic type at compile time. In InterceptAsyncCore, you can await your m_TokenService.GetTokenAsync call, then call methodToInvoke.Invoke. You then cast the result of Invoke to Task and await it. You will need a try/catch around that. Once it's completed, you use reflection to get the Result from the Task and call tcs.SetResult. If it threw an exception, you call tcs.SetException.
    So what you end up with is the Intercept method calls InterceptAsync which returns a Task object (backed by a TaskCompletionSource) which will complete with the result of the call when that call finishes. This task is set as the result and the original caller now has a Task which completes with the correct final result.

  10. r-pankevicius commented on Mar 30, 2019

    @r-pankevicius

    @mconnew Thanks, I found IClientMessageInspector in WCF.
    Yes, that threading stuff needs more work.
    Thanks a lot for the code review, BTW.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions