I'm trying to create an interceptor for the gRPC client that the configured API token is always set.
The problem is that I just can't find a way to set the context.Options.Headers. If I'm reading the docs I need to call the WithHeaders method and that needs to set the new MetaData so I can add more headers. The only problem is that still doesn't create the headers object.
Does anyone know what I'm doing wrong?
Docs:
https://grpc.github.io/grpc/csharp/api/Grpc.Core.CallOptions.html?q=calloptions
Code:
using System;
using Floof.Common.Exceptions.IoC;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Floof.Common.GrpcClient.Interceptors
{
public class AuthorizationHeaderInterceptor : Interceptor
{
private readonly ILogger<AuthorizationHeaderInterceptor> _logger;
public const string Section = "gRPC";
public const string Key = "ApiKey";
private const string AuthorizationHeader = "Authorization";
private readonly string _apiToken;
public AuthorizationHeaderInterceptor(
ILogger<AuthorizationHeaderInterceptor> logger,
IConfiguration configuration
)
{
if (configuration == null)
throw new ArgumentNullException(nameof(configuration));
_logger = logger;
var apiToken = configuration.GetSection(Section)?[Key];
if (string.IsNullOrWhiteSpace(apiToken))
throw new IoCConfigurationException("API key", Section, Key);
_apiToken = apiToken;
}
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation
)
{
// Check if the headers are not null, if so, initialize new headers
if (context.Options.Headers == null)
{
_logger.LogDebug("Adding gRPC option headers");
context.Options.WithHeaders(new Metadata());
}
// gRPC calls and responses can also include metadata that's similar to HTTP headers. This metadata is mostly
// invisible to gRPC itself and is passed through to be processed by your application code or middleware.
// Metadata is represented as key/value pairs, where the key is a string and the value is either a string or
// binary data. You don’t need to specify metadata in the .proto file.
// https://learn.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/metadata
var authorization = new Metadata.Entry(AuthorizationHeader, _apiToken);
// Check if the header already has an Authorization header within. For now we agreed on that no one is allowed
// to set this header. If we want to use this client for external API services as well, we need to look up if
// this is a good client to use and what changes it takes for this client to pair with another API then the Floof cloud
var setAuthorizationHeaderEntry = context.Options.Headers.Get(AuthorizationHeader);
if (setAuthorizationHeaderEntry != null)
{
_logger.LogWarning("Replacing the Authorization header by the configured Floof API key value.");
// Remove the header out of the options because we set the configured key
context.Options.Headers.Remove(setAuthorizationHeaderEntry);
}
// Add the header to the context.
context.Options.Headers.Add(authorization);
// replace the context with the authorization context
return base.AsyncUnaryCall(request, context, continuation);
}
}
}
Trying to pass new context new ClientInterceptorContext with all needed headers set when calling return base.AsyncUnaryCall should help:
public class AuthorizationHeaderInterceptor : Interceptor
{
...
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
...
var headers = new Metadata();
headers.Add(new Metadata.Entry("Authorization", _token));
var newOptions = context.Options.WithHeaders(headers);
var newContext = new ClientInterceptorContext<TRequest, TResponse>(
context.Method,
context.Host,
newOptions);
return base.AsyncUnaryCall(request, newContext, continuation);
}
}
https://github.com/grpc/grpc-dotnet/issues/1255#issuecomment-811635583
Related
I am working on a Blazor project, and to make the question I have easier to understand, we can say that I am using two different services that handles the Authentication part. Those are registered in the configureservices startup method together with a named httpclient.
services.AddHttpClient("XBOWServicesApi", c =>
{
c.BaseAddress = new Uri(XBOWServicesApi);
});
services.AddSingleton<IService1, Service1>();
services.AddSingleton<IService2, Service2>();
Service 1: Wraps all functionality available in a REST Api. It uses an http client which is set in the constructor via an instanciated httpclientfactory. This needs to be set with a baseurl and an Auth-header to work.
public Service1(IHttpClientFactory clientFactory)
{
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
Service 2: Handles the login/logout functionality using a custom AuthenticationStateProvider. It has its own httpclient, so that I can set the Auth Header for the http client. The constructor works in the same way as for Service 1.
public Service2(IHttpClientFactory clientFactory)
{
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
The reason for this build up is of course that I like to share the same http client, so when it is set in the login/logout methods, service 1 will have the correct auth header when communicating with the api.
However, the client factory provides a new instance everytime, so this will never work.
Any ideas how to handle this?
/Henrik
You can use named client:
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Afterwards, just call CreateClient method with corresponding name parameter.
var client = _clientFactory.CreateClient("github");
Each time CreateClient is called:
A new instance of HttpClient is created.
The configuration action is
called.
You can find more details in Microsoft documentation here.
When I read through the Microsoft IHttpClientFactory docs:
Each time you get an HttpClient object from the IHttpClientFactory, a
new instance is returned. But each HttpClient uses an
HttpMessageHandler that's pooled and reused by the IHttpClientFactory
to reduce resource consumption, as long as the HttpMessageHandler's
lifetime hasn't expired.
Does that answer your question?
You can share scoped services between transient HttpClients by using HttpMessageHandlers.
IHttpClient.CreateClient returns a new instance every time, but you can register a HttpMessageHandler as shown below:
services.AddScoped<HandlerData>();
services.AddTransient<HeaderHandler>();
services.AddHttpClient("XBOWServicesApi", c =>
{
c.BaseAddress = new Uri(XBOWServicesApi);
}).AddHttpMessageHandler<HeaderHandler>();
HeaderHandler Class:
public class HeaderHandler : DelegatingHandler
{
private readonly IHttpContextAccessor httpContextAccessor;
public HeaderHandler(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken )
{
var Data= this.httpContextAccessor.HttpContext.RequestServices.GetRequiredService<HandlerData>();
request.Headers.Add(Data.HeaderName, Data.HeaderValue);
return base.SendAsync(request, cancellationToken);
}
}
HandlerData Class:
public class HandlerData
{
public string HeaderName { get; set; }
public string HeaderValue { get; set; }
}
ServicesCode:
public Service1(IHttpClientFactory clientFactory, HandlerData data)
{
data.HeaderName = "Header1";
data.HeaderValue = "Value";
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
public Service2(IHttpClientFactory clientFactory)
{
//This will contain the same headers as Service1 as HandlerData is Scoped Service
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
Alternatively, you can also use new IHttpMessageHandlerFactory if you need to create handlers that live in the same DI scope as you request:
Reference: https://github.com/aspnet/HttpClientFactory/issues/166
I am using Refit and would like to set OPTIONAL dynamic headers for some methods. For example if the user is logged in, I want to have header "UserId" and "AuthenticationToken", otherwise do NOT set the headers
[Post("/search")]
Task<SearchResponse> Search([Body]SearchRequest request, [Header("UserId")] string userId,[Header("AuthorizationToken")] string token);
Not sure if I pass null value to userId and token, the two headers will have null value or just be skipped (not included in the header)?
Thanks.
Using refit you can implement DelegatingHandler and then register that to do whatever you need to the http request before it is sent on. Here is adding an origin header to each request. Refit interface does not need to worry about it.
public class AddOriginHeaderToRequest : DelegatingHandler
{
private const string ServiceNameSettingLocation = "AppConfig:Logging:ServiceName";
private readonly IHttpContextAccessor httpContextAccessor;
private readonly IConfiguration configuration;
public AddOriginHeaderToRequest(IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
{
this.httpContextAccessor = httpContextAccessor;
this.configuration = configuration;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var origin = this.configuration[AddOriginHeaderToRequest.SomethingThatShouldBeDefined];
if (!(request.Headers.Contains("origin") || request.Headers.Contains("Origin")) && origin != null)
{
request.Headers.Add("origin", origin);
}
return await base.SendAsync(request, cancellationToken);
}
}
Then register it like this:
services.AddTransient<AddOriginHeaderToRequest>();
Then the refit client can be registered like this (this is a redacted version of one of our nuget packages so will hopefully give an idea of how it works):
public static IHttpClientBuilder AddHttpClientWithDefaultHandlers(
this IServiceCollection services,
string name,
Action<HttpClient> configureClient)
{
return services.AddHttpClient(name, configureClient)
.AddHttpMessageHandler<AddOriginHeaderToRequest>();
}
Then in our service we register our refit handler like this:
services.AddHttpClientWithRefitAndDefaultHandlers<ImyHttpClient>(
"myHttpClient",
c =>
{
c.BaseAddress = new Uri(appSettings.Value.AppConfig.SomeUrl);
});
This can be simplified but we have a number of different handlers that massage our http requests in a standard way.
I hope that gives you a pointer to how it could work.
Basically what I want to do:
Client-Side (get a token, attach is as a metadata-token and send it to the different services) -- DONE
Server-side (get the token, verify issuer, date and audience) -- DONE
Server-side (After verifying the token, I would like to populate the fields of the AuthContext, so that I can use them in the my GrpcServices) -- Need help here
So Far I manage to return a ClaimsPrinciple from my tokenChallenger.GetClaimsPrincipal(token) method, however I am unsure how to populate the AuthContext.
I was reading the documentation , and I basically need an interceptor on the server side.
Here is my code so far
public class AuthInterceptor: Interceptor
{
private readonly JwtTokenChallenger _tokenChallenger;
public AuthInterceptor(JwtTokenChallenger tokenChallenger)
{
_tokenChallenger = tokenChallenger;
}
public override Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
Task<TResponse> response;
var isThisProtectedMethodAttribute = IsThisProtectedMethod(continuation.Target.GetType(), context);
if (isThisProtectedMethodAttribute == null) //only protected methods have attributes.
{
response = continuation(request, context);
return response;
}
//jwt token validation;
//populate auth context with claims principle?
var token = context.RequestHeaders.FirstOrDefault(h => h.Key == "authorization").Value.Split(" ").Last();
if (token == null)
{
context.Status = new Status(StatusCode.Unauthenticated, "Invalid token");
return default(Task<TResponse>);
}
if (ValidateToken(token))
{
PopulateAuthContext(token, context);
return continuation(request, context);
}
context.Status = new Status(StatusCode.Unauthenticated, "Invalid token");
return default(Task<TResponse>);
//test
}
private ProtectedMethod IsThisProtectedMethod(Type t, ServerCallContext context)
{
List<ProtectedMethod> returnAttributes = new List<ProtectedMethod>();
Attribute[] attrs = Attribute.GetCustomAttributes(t);
foreach (Attribute attr in attrs)
{
if (attr is ProtectedMethod a && (a.ProtectedResourceAcceessMethodName == context.Method.Split("/").Last()))
{
return a;
}
}
return null;
}
private bool ValidateToken(String tokenToValidate)
{
return _tokenChallenger.isValidToken(tokenToValidate);
}
private void PopulateAuthContext(String token, ServerCallContext context)
{
//help needed?
}
}
Client-side I use Java (Android), Server-side I use C#
Edit: token has 2 things that I want, nameidentifier and roles
The gRPC C# API doesn't allow you to populate AuthContext, the AuthContext can only be populated by gRPC internally (if you use TLS certificate-based authentication).
You basically have two options here:
you can populate the request metadata of the request with additional entries you need to pass to the actual method handlers (server-side interceptor can modify the metadata). Note: if you decide to populate the metadata, you need to be very careful about making sure that a malicious client can't send corresponding metadata entries along with her request and thus fake that she's been authenticated. You can do that e.g. by adding one more interceptor that strips all the sensitive headers from all incoming requests.
your interceptor can set the auth-related values into an execution context that's compatible with async/await. That way, the values will be accessible from the async methods that implement the server-side behavior. See e.g. https://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html for more info on C# contexts.
There is a third option as well, populate a new derived class of the ServerCallContext, with a new AuthContext populated with your claims. You can cache the transformation from the token
I am trying to figure out how to best use the HttpClient class in ASP.Net Core.
According to the documentation and several articles, the class is best instantiated once for the lifetime of the application and shared for multiple requests. Unfortunately, I could not find an example of how to correctly do this in Core so I’ve come up with the following solution.
My particular needs require the use of 2 different endpoints (I have an APIServer for business logic and an API driven ImageServer), so my thinking is to have 2 HttpClient singletons that I can use in the application.
I’ve configured my servicepoints in the appsettings.json as follows:
"ServicePoints": {
"APIServer": "http://localhost:5001",
"ImageServer": "http://localhost:5002",
}
Next, I created a HttpClientsFactory that will instantiate my 2 httpclients and hold them in a static Dictionary.
public class HttpClientsFactory : IHttpClientsFactory
{
public static Dictionary<string, HttpClient> HttpClients { get; set; }
private readonly ILogger _logger;
private readonly IOptions<ServerOptions> _serverOptionsAccessor;
public HttpClientsFactory(ILoggerFactory loggerFactory, IOptions<ServerOptions> serverOptionsAccessor) {
_logger = loggerFactory.CreateLogger<HttpClientsFactory>();
_serverOptionsAccessor = serverOptionsAccessor;
HttpClients = new Dictionary<string, HttpClient>();
Initialize();
}
private void Initialize()
{
HttpClient client = new HttpClient();
// ADD imageServer
var imageServer = _serverOptionsAccessor.Value.ImageServer;
client.BaseAddress = new Uri(imageServer);
HttpClients.Add("imageServer", client);
// ADD apiServer
var apiServer = _serverOptionsAccessor.Value.APIServer;
client.BaseAddress = new Uri(apiServer);
HttpClients.Add("apiServer", client);
}
public Dictionary<string, HttpClient> Clients()
{
return HttpClients;
}
public HttpClient Client(string key)
{
return Clients()[key];
}
}
Then, I created the interface that I can use when defining my DI later on. Notice that the HttpClientsFactory class inherits from this interface.
public interface IHttpClientsFactory
{
Dictionary<string, HttpClient> Clients();
HttpClient Client(string key);
}
Now I am ready to inject this into my Dependency container as follows in the Startup class under the ConfigureServices method.
// Add httpClient service
services.AddSingleton<IHttpClientsFactory, HttpClientsFactory>();
All is now set-up to start using this in my controller.
Firstly, I take in the dependency. To do this I created a private class property to hold it, then add it to the constructor signature and finish by assigning the incoming object to the local class property.
private IHttpClientsFactory _httpClientsFactory;
public AppUsersAdminController(IHttpClientsFactory httpClientsFactory)
{
_httpClientsFactory = httpClientsFactory;
}
Finally, we can now use the Factory to request a htppclient and execute a call. Below, an example where I request an image from the imageserver using the httpclientsfactory:
[HttpGet]
public async Task<ActionResult> GetUserPicture(string imgName)
{
// get imageserver uri
var imageServer = _optionsAccessor.Value.ImageServer;
// create path to requested image
var path = imageServer + "/imageuploads/" + imgName;
var client = _httpClientsFactory.Client("imageServer");
byte[] image = await client.GetByteArrayAsync(path);
return base.File(image, "image/jpeg");
}
Done!
I’ve tested this and it work great on my development environment. However, I am not sure if this is the best way to implement this. I remain with the following questions:
Is this solution thread safe? (according to the MS doc: ‘Any public static (Shared in Visual Basic) members of this type are thread safe.’)
Will this set-up be able to handle a heavy load without opening many separate connection?
What to do in ASP.Net core to handle the DNS problem described in ‘Singleton HttpClient? Beware of this serious behaviour and how to fix.’ located at http://byterot.blogspot.be/2016/07/singleton-httpclient-dns.html
Any other improvements or suggestions?
If using .net core 2.1 or higher, the best approach would be to use the new HttpClientFactory. I guess Microsoft realized all the issues people were having so they did the hard work for us. See below for how to set it up.
NOTE: Add a reference to Microsoft.Extensions.Http.
1 - Add a class that uses HttpClient
public interface ISomeApiClient
{
Task<HttpResponseMessage> GetSomethingAsync(string query);
}
public class SomeApiClient : ISomeApiClient
{
private readonly HttpClient _client;
public SomeApiClient (HttpClient client)
{
_client = client;
}
public async Task<SomeModel> GetSomethingAsync(string query)
{
var response = await _client.GetAsync($"?querystring={query}");
if (response.IsSuccessStatusCode)
{
var model = await response.Content.ReadAsJsonAsync<SomeModel>();
return model;
}
// Handle Error
}
}
2 - Register your clients in ConfigureServices(IServiceCollection services) in Startup.cs
var someApiSettings = Configuration.GetSection("SomeApiSettings").Get<SomeApiSettings>(); //Settings stored in app.config (base url, api key to add to header for all requests)
services.AddHttpClient<ISomeApiClient, SomeApiClient>("SomeApi",
client =>
{
client.BaseAddress = new Uri(someApiSettings.BaseAddress);
client.DefaultRequestHeaders.Add("api-key", someApiSettings.ApiKey);
});
3 - Use the client in your code
public class MyController
{
private readonly ISomeApiClient _client;
public MyController(ISomeApiClient client)
{
_client = client;
}
[HttpGet]
public async Task<IActionResult> GetAsync(string query)
{
var response = await _client.GetSomethingAsync(query);
// Do something with response
return Ok();
}
}
You can add as many clients and register as many as needed in your startup with services.AddHttpClient
Thanks to Steve Gordon and his post here for helping me use this in my code!
In answer to a question from #MuqeetKhan regarding using authentication with the httpclient request.
Firstly, my motivation to use DI and a factory was to allow me to extend my application easily to different and multiple API’s and have easy access to that throughout my code. It’s a template I hope to be able to reuse multiple times.
In the case of my ‘GetUserPicture’ controller decribed in the original question above, I indeed for simplicity reasons removed the authentication. Honestly however, I am still in doubt if I need it there to simply retrieve an image from the imageserver. Anyhow, in other controllers I definitely do need it, so…
I’ve implemented Identityserver4 as my authentication server. This provides me with the authentication on top of ASP Identity.
For authorization (using roles in this case), I implemented IClaimsTransformer in my MVC ‘and’ API projects (you can read more about this here at How to put ASP.net Identity Roles into the Identityserver4 Identity token).
Now, the moment I enter my controller I have an authenticated and authorized user for which I can retrieve an access token. I use this token to call my api which is of course calling the same instance of identityserver to verify if the user is authenticated.
The last step is to allow my API to verify if the user is authorized to call the requested api controller. In the request pipeline of the API using IClaimsTransformer as explained before, I retrieve the authorization of the calling user and add it to the incoming claims.
Note that in case of an MVC calling and API, I thus retrieve the authorization 2 times; once in the MVC request pipeline and once in the API request pipeline.
Using this set-up I am able to use my HttpClientsFactory with Authorization and Authentication.
On big security part I am missing is HTTPS of course. I hope I can somehow add it to my factory. I'll update it once I've implemented it.
As always, any suggestions are welcome.
Below an example where I upload an image to the Imageserver using authentication (user must be logged in and have role admin).
My MVC controller calling the ‘UploadUserPicture’:
[Authorize(Roles = "Admin")]
[HttpPost]
public async Task<ActionResult> UploadUserPicture()
{
// collect name image server
var imageServer = _optionsAccessor.Value.ImageServer;
// collect image in Request Form from Slim Image Cropper plugin
var json = _httpContextAccessor.HttpContext.Request.Form["slim[]"];
// Collect access token to be able to call API
var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
// prepare api call to update image on imageserver and update database
var client = _httpClientsFactory.Client("imageServer");
client.DefaultRequestHeaders.Accept.Clear();
client.SetBearerToken(accessToken);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("image", json[0])
});
HttpResponseMessage response = await client.PostAsync("api/UserPicture/UploadUserPicture", content);
if (response.StatusCode != HttpStatusCode.OK)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
return StatusCode((int)HttpStatusCode.OK);
}
API handling the user upload
[Authorize(Roles = "Admin")]
[HttpPost]
public ActionResult UploadUserPicture(String image)
{
dynamic jsonDe = JsonConvert.DeserializeObject(image);
if (jsonDe == null)
{
return new StatusCodeResult((int)HttpStatusCode.NotModified);
}
// create filname for user picture
string userId = jsonDe.meta.userid;
string userHash = Hashing.GetHashString(userId);
string fileName = "User" + userHash + ".jpg";
// create a new version number
string pictureVersion = DateTime.Now.ToString("yyyyMMddHHmmss");
// get the image bytes and create a memory stream
var imagebase64 = jsonDe.output.image;
var cleanBase64 = Regex.Replace(imagebase64.ToString(), #"^data:image/\w+;base64,", "");
var bytes = Convert.FromBase64String(cleanBase64);
var memoryStream = new MemoryStream(bytes);
// save the image to the folder
var fileSavePath = Path.Combine(_env.WebRootPath + ("/imageuploads"), fileName);
FileStream file = new FileStream(fileSavePath, FileMode.Create, FileAccess.Write);
try
{
memoryStream.WriteTo(file);
}
catch (Exception ex)
{
_logger.LogDebug(LoggingEvents.UPDATE_ITEM, ex, "Could not write file >{fileSavePath}< to server", fileSavePath);
return new StatusCodeResult((int)HttpStatusCode.NotModified);
}
memoryStream.Dispose();
file.Dispose();
memoryStream = null;
file = null;
// update database with latest filename and version
bool isUpdatedInDatabase = UpdateDatabaseUserPicture(userId, fileName, pictureVersion).Result;
if (!isUpdatedInDatabase)
{
return new StatusCodeResult((int)HttpStatusCode.NotModified);
}
return new StatusCodeResult((int)HttpStatusCode.OK);
}
For situations when you can't use DI:
using System.Net.Http;
public class SomeClass
{
private static readonly HttpClient Client;
static SomeClass()
{
var handler = new SocketsHttpHandler
{
// Sets how long a connection can be in the pool to be considered reusable (by default - infinite)
PooledConnectionLifetime = TimeSpan.FromMinutes(1),
};
Client = new HttpClient(handler, disposeHandler: false);
}
...
}
Reference https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0#alternatives-to-ihttpclientfactory
I have a WCF service which requires an application ID parameter to be passed to each service call. Currently my exposed methods require a parameter. I want to try and push this information into the Channel headers. My WCF is hosted using Net.tcp. Here is my client proxy code:
public class CustomerClient : ClientBase<ICustomerBrowser>, ICustomerBrowser
{
public Customer Get(string ApplicationID, string CustomerId)
{
try
{
using (OperationContextScope _scope = new OperationContextScope(this.InnerChannel))
{
MessageHeader _header = MessageHeader.CreateHeader("AppID", string.Empty, ApplicationId);
OperationContext.Current.OutgoingMessageHeaders.Add(_header);
return Channel.Get(ApplicationId, CustomerId);
// return Channel.Get(CustomerId);
}
}
}
}
(The commented out line is what I want to use going forward).
Server code:
var _context = WebOperationContext.Current;
var h = _context.IncomingRequest.Headers;
In the _context object there are private methods containing my header, but publicly in the _context.IncomingRequest.Headers I get this:
There is no HttpRequestMessageProperty on the incoming Message.
So my question is, am I suffering because I am not hosting on HTTP? Is there a way to trick the server to give me access to those headers by adding some pseudo HTTP headers? Or can I get at the private members maybe via reflection?
You are using the wrong instance of an OperationContext.
The WebOperationContext is specialized for messages that are transported over http. It expects its headers to have a specif name. In the case of WebOperationContext the MessageHeaders dictionary expects a key named httpRequest, which isn't provided in your scenario.
As you're using the standard OperationContext client side should do the same server side:
var _context = OperationContext.Current;
var headers = _context.IncomingMessageHeaders;
foreach (MessageHeaderInfo h in headers)
{
if (h.Name == "AppID") {
Debug.WriteLine(h.ToString());
}
}