I would like wrap my web api controller response in a generic class and can't work out how to get the actual type from HttpResponseMessage.Content.
e.g. My Controller
[ResponseType(typeof(ApiAuthentication))]
public IHttpActionResult PostLogOnTest()
{
ApiAuthentication loginResponse = new ApiAuthentication();
return Ok(loginResponse);
}
I need to be able to wrap all the controller response in a 'data' container. I could code this response class into each method but I thought it would be nice to do in common place? So I have a class
[DataContract]
public class ApiResponse<T>
{
/// <summary>
/// Gets or sets the data.
/// </summary>
/// <value>
/// The data.
/// </value>
[DataMember(Name="data")]
public T Data { get; set; }
}
I can hook into a MessageHandler and get the HttpResponseMessage but when I try to get the Content from .Content - it comes as a ObjectContent<> - i just want the ApiAuthentication so i can then create new HttpResponseMessage>
Is there any way to do this or easier way?
Thanks
Related
In my controller there is a method take accepts one route and and one query parameter as arguments:
/// <summary>
/// My Method
/// </summary>
/// <param name="routeParameter">Nice description of route parameter.</param>
/// <param name="queryParameter">Nice description of query paramter.</param>
[HttpPost("somePath/{routeParameter}")]
public IActionResult MyMethod([FromRoute] string routeParameter, [FromQuery] DateTime queryParamter)
{
// do something
}
In the OpenApi.json / Swagger generated from this signature via Swashbuckle XML the routeParameter (path) is always required but the queryParameter (query) is marked as optional.
How can I mark the query parameter as required, too?
How can I mark the query parameter as required, too?
Just use [FromQuery, BindRequired] as follow:
[HttpPost("somePath/{routeParameter}")]
public IActionResult MyMethod([FromRoute] string routeParameter, [FromQuery, BindRequired] DateTime queryParamter)
{
// do something
}
Here is the test result:
Here is an alternative with [FromUri] to receive your parameters
[HttpPost("somePath")]
public IActionResult MyMethod([FromUri] Paging paging, [FromUri] QueryParam param)
{
// do something
}
QueryParam.cs
public class QueryParam
{
[Required]
public string routeParameter{ get; set; }
}
Swagger UI
Not sure what is going on here.
I am exposing the Identity functionality through a Web API project. The CRUD aspect will be exposed to an admin app and the login, registration in a public facing app.
Right now I am just trying to return a list of all users in the database through a Web Api controller action. I am getting nothing output to the response, but I do get back data from the service:
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("")]
public async Task<IHttpActionResult> GetAllUsers()
{
var model = await _userService.GetAllUsers(); //<---Gets List<AppUser> here?
return Ok(model);
}
This action shows nothing on fiddler or Postman?
Any ideas?
public class AppUser : IdentityUser
{
public DateTime Created { get; set; }
}
Is there something special about the IdentityUser class that prevents it from being serialized?
Here is the web api serialization config:
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
config.Formatters.Add(new JsonFormatter());
}
public class JsonFormatter : JsonMediaTypeFormatter
{
public JsonFormatter()
{
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
this.SerializerSettings.Formatting = Formatting.Indented;
}
public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
{
base.SetDefaultContentHeaders(type, headers, mediaType);
headers.ContentType = new MediaTypeHeaderValue("application/json");
}
}
Found my answer. The IdentityUser class is not really meant to be exposed over an API; lots of sensitive data and all.
However this is will sit behind a firewall and I do not feel like writing a DTO and mapper just to make this work.
The answer is explained here
Basically you just need to override the properties you want exposed and decorate them with a DataMember attribute for serialization.
I'm using a generic return type for all my api responses:
public HttpStatusCode statusCode { get; set; }
public string error { get; set; }
public IDictionary<string, string> errorfor { get; set; }
public T result { get; set; }
and in API:
/// <summary>
/// GET Order API
/// </summary>
/// <returns> return list of orders {Order} </returns>
public HttpResponseMessage Get(){
var response = new BaseResponseMessage<IList<Order>>();
//some more codes
response.result = orders;
return Request.CreateResponse(HttpStatusCode.OK, response);
}
Now of course my API Help page don't show Order in sample Response body. Is it possible to configure Help Page generator to show generic type? Thanks!
Web API 2.1 Help Pages now do this: http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-21#help-page
You might also check out returning the standard HttpResponseMessage with the new ResponseTypeAttribute:
/// <summary>
/// GET Order API
/// </summary>
[ResponseType(typeof(List<Order>))]
public HttpResponseMessage Get()
{
var orders = new List<Order>();
return Request.CreateResponse(HttpStatusCode.OK, orders);
}
It would not be possible for HelpPage to guess what would be the return type of an action with just the signature that you have. As you know HelpPage generation does not happen during normal request, but is something which depends on static information.
There is a workaround for this though. You can look at the following commented code in Areas\HelpPage\App_Start\HelpPageConfig.cs which lets you specify a specific return type for an action.
//// Uncomment the following to correct the sample response when the action returns an HttpResponseMessage with ObjectContent<string>.
//// The sample will be generated as if the controller named "Values" and action named "Post" were returning a string.
//config.SetActualResponseType(typeof(string), "Values", "Post");
I understand that this workaround would be too cumbersome for you since you mentioned about having this kind of signature for all your actions.
You could probably extend HelpPage by creating a custom attribute which informs about the return type and decorate it on the actions. You can then modify installed HelpPage code to look for these attributes.
I am learning WCF so my apologies if my terminology is off here and there.
I am trying to deserialize a soap message that was sent with WCF and captured by Fiddler and I keep getting this error message: "The data at the root level is invalid. Line 1, position 1."
The WCF service is setup with the following code:
// Set up WcfProxy DataPortal service url
Csla.DataPortalClient.WcfProxy.DefaultUrl = appRootUrl + "WcfSilverlightPortal.svc";
// Set up WcfProxy Binding
System.ServiceModel.BasicHttpBinding largeBinding = new System.ServiceModel.BasicHttpBinding();
largeBinding.MaxReceivedMessageSize = 20000000;
largeBinding.MaxBufferSize = 20000000;
largeBinding.OpenTimeout = new TimeSpan(0, 10, 0);
largeBinding.ReceiveTimeout = new TimeSpan(0, 10, 0);
largeBinding.CloseTimeout = new TimeSpan(0, 10, 0);
largeBinding.SendTimeout = new TimeSpan(0, 10, 0);
largeBinding.Name = "BasicHttpBinding_IWcfPortal";
Csla.DataPortalClient.WcfProxy.DefaultBinding = largeBinding;
The soap message captured in Fiddler is as follows:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<Fetch xmlns="http://ws.lhotka.net/WcfDataPortal">
<request xmlns:d4p1="http://schemas.datacontract.org/2004/07/Csla.Server.Hosts.Silverlight" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<d4p1:ClientContext>QBhBcnJheU9mU2VyaWFsaXphdGlvbkluZm8IQWh0dHA6Ly9zY2hlbWFzLmRhdGFjb250cmFjdC5vcmcvMjAwNC8wNy9Dc2xhLlNlcmlhbGl6YXRpb24uTW9iaWxlCQFpKWh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlQBFTZXJpYWxpemF0aW9uSW5mb0AIQ2hpbGRyZW4JAWE5aHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS8yMDAzLzEwL1NlcmlhbGl6YXRpb24vQXJyYXlzAUALUmVmZXJlbmNlSWSDQAhUeXBlTmFtZZhlQ3NsYS5Db3JlLkNvbnRleHREaWN0aW9uYXJ5LCBDc2xhLCBWZXJzaW9uPTQuMy4xMy4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTkzYmU1ZmRjMDkzZTRjMzABQAZWYWx1ZXMJAWE5aHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS8yMDAzLzEwL1NlcmlhbGl6YXRpb24vQXJyYXlzAQEB</d4p1:ClientContext>
<d4p1:ClientCulture>en-US</d4p1:ClientCulture>
<d4p1:ClientUICulture>en-US</d4p1:ClientUICulture>
<d4p1:CriteriaData i:nil="true" />
<d4p1:GlobalContext>QBhBcnJheU9mU2VyaWFsaXphdGlvbkluZm8IQWh0dHA6Ly9zY2hlbWFzLmRhdGFjb250cmFjdC5vcmcvMjAwNC8wNy9Dc2xhLlNlcmlhbGl6YXRpb24uTW9iaWxlCQFpKWh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlQBFTZXJpYWxpemF0aW9uSW5mb0AIQ2hpbGRyZW4JAWE5aHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS8yMDAzLzEwL1NlcmlhbGl6YXRpb24vQXJyYXlzAUALUmVmZXJlbmNlSWSDQAhUeXBlTmFtZZhlQ3NsYS5Db3JlLkNvbnRleHREaWN0aW9uYXJ5LCBDc2xhLCBWZXJzaW9uPTQuMy4xMy4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTkzYmU1ZmRjMDkzZTRjMzABQAZWYWx1ZXMJAWE5aHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS8yMDAzLzEwL1NlcmlhbGl6YXRpb24vQXJyYXlzAQEB</d4p1:GlobalContext>
<d4p1:Principal>QBhBcnJheU9mU2VyaWFsaXphdGlvbkluZm8IQWh0dHA6Ly9zY2hlbWFzLmRhdGFjb250cmFjdC5vcmcvMjAwNC8wNy9Dc2xhLlNlcmlhbGl6YXRpb24uTW9iaWxlCQFpKWh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlQBFTZXJpYWxpemF0aW9uSW5mb0AIQ2hpbGRyZW4JAWE5aHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS8yMDAzLzEwL1NlcmlhbGl6YXRpb24vQXJyYXlzAUALUmVmZXJlbmNlSWSDQAhUeXBlTmFtZZhwQ3NsYS5TZWN1cml0eS5VbmF1dGhlbnRpY2F0ZWRQcmluY2lwYWwsIENzbGEsIFZlcnNpb249NC4zLjEzLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49OTNiZTVmZGMwOTNlNGMzMAFABlZhbHVlcwkBYTlodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tLzIwMDMvMTAvU2VyaWFsaXphdGlvbi9BcnJheXNeM0tleVZhbHVlT2ZzdHJpbmdTZXJpYWxpemF0aW9uSW5mby5GaWVsZERhdGFPem9adkxybV4DS2V5mRZDc2xhUHJpbmNpcGFsLklkZW50aXR5XgVWYWx1ZUAMRW51bVR5cGVOYW1lLgNuaWyGAUAHSXNEaXJ0eYVABVZhbHVlLgR0eXBlmA5iOmJhc2U2NEJpbmFyeQkBYiBodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYaCNCUAYQXJyYXlPZlNlcmlhbGl6YXRpb25JbmZvCEFodHRwOi8vc2NoZW1hcy5kYXRhY29udHJhY3Qub3JnLzIwMDQvMDcvQ3NsYS5TZXJpYWxpemF0aW9uLk1vYmlsZQkBaSlodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZUARU2VyaWFsaXphdGlvbkluZm9ACENoaWxkcmVuCQFhOWh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vMjAwMy8xMC9TZXJpYWxpemF0aW9uL0FycmF5c14zS2V5VmFsdWVPZnN0cmluZ1NlcmlhbGl6YXRpb25JbmZvLkNoaWxkRGF0YU96b1p2THJtXgNLZXmZDV9maWVsZE1hbmFnZXJeBVZhbHVlQAdJc0RpcnR5hUALUmVmZXJlbmNlSWSJAgEBAUALUmVmZXJlbmNlSWSDQAhUeXBlTmFtZZhvQ3NsYS5TZWN1cml0eS5VbmF1dGhlbnRpY2F0ZWRJZGVudGl0eSwgQ3NsYSwgVmVyc2lvbj00LjMuMTMuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj05M2JlNWZkYzA5M2U0YzMwAUAGVmFsdWVzCQFhOWh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vMjAwMy8xMC9TZXJpYWxpemF0aW9uL0FycmF5cwEBQBFTZXJpYWxpemF0aW9uSW5mb0AIQ2hpbGRyZW4JAWE5aHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS8yMDAzLzEwL1NlcmlhbGl6YXRpb24vQXJyYXlzXjNLZXlWYWx1ZU9mc3RyaW5nU2VyaWFsaXphdGlvbkluZm8uQ2hpbGREYXRhT3pvWnZMcm1eA0tleZkFUm9sZXNeBVZhbHVlQAdJc0RpcnR5hUALUmVmZXJlbmNlSWSJAwEBAUALUmVmZXJlbmNlSWSJAkAIVHlwZU5hbWWYcUNzbGEuQ29yZS5GaWVsZE1hbmFnZXIuRmllbGREYXRhTWFuYWdlciwgQ3NsYSwgVmVyc2lvbj00LjMuMTMuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj05M2JlNWZkYzA5M2U0YzMwAUAGVmFsdWVzCQFhOWh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vMjAwMy8xMC9TZXJpYWxpemF0aW9uL0FycmF5c14zS2V5VmFsdWVPZnN0cmluZ1NlcmlhbGl6YXRpb25JbmZvLkZpZWxkRGF0YU96b1p2THJtXgNLZXmZE19idXNpbmVzc09iamVjdFR5cGVeBVZhbHVlQAxFbnVtVHlwZU5hbWUuA25pbIYBQAdJc0RpcnR5hUAFVmFsdWUuBHR5cGWYCGI6c3RyaW5nCQFiIGh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hmG9Dc2xhLlNlY3VyaXR5LlVuYXV0aGVudGljYXRlZElkZW50aXR5LCBDc2xhLCBWZXJzaW9uPTQuMy4xMy4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTkzYmU1ZmRjMDkzZTRjMzABAQFeM0tleVZhbHVlT2ZzdHJpbmdTZXJpYWxpemF0aW9uSW5mby5GaWVsZERhdGFPem9adkxybV4DS2V5mRJBdXRoZW50aWNhdGlvblR5cGVeBVZhbHVlQAxFbnVtVHlwZU5hbWUuA25pbIYBQAdJc0RpcnR5hUAFVmFsdWUuBHR5cGWYCGI6c3RyaW5nCQFiIGh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hAQEBXjNLZXlWYWx1ZU9mc3RyaW5nU2VyaWFsaXphdGlvbkluZm8uRmllbGREYXRhT3pvWnZMcm1eA0tleZkPSXNBdXRoZW50aWNhdGVkXgVWYWx1ZUAMRW51bVR5cGVOYW1lLgNuaWyGAUAHSXNEaXJ0eYVABVZhbHVlLgR0eXBlmAliOmJvb2xlYW4JAWIgaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWGFAQFeM0tleVZhbHVlT2ZzdHJpbmdTZXJpYWxpemF0aW9uSW5mby5GaWVsZERhdGFPem9adkxybV4DS2V5mQROYW1lXgVWYWx1ZUAMRW51bVR5cGVOYW1lLgNuaWyGAUAHSXNEaXJ0eYVABVZhbHVlLgR0eXBlmAhiOnN0cmluZwkBYiBodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYQEBAQEBQBFTZXJpYWxpemF0aW9uSW5mb0AIQ2hpbGRyZW4JAWE5aHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS8yMDAzLzEwL1NlcmlhbGl6YXRpb24vQXJyYXlzAUALUmVmZXJlbmNlSWSJA0AIVHlwZU5hbWWYvkNzbGEuQ29yZS5Nb2JpbGVMaXN0YDFbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTUuMC41LjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49N2NlYzg1ZDdiZWE3Nzk4ZV1dLCBDc2xhLCBWZXJzaW9uPTQuMy4xMy4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTkzYmU1ZmRjMDkzZTRjMzABQAZWYWx1ZXMJAWE5aHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS8yMDAzLzEwL1NlcmlhbGl6YXRpb24vQXJyYXlzXjNLZXlWYWx1ZU9mc3RyaW5nU2VyaWFsaXphdGlvbkluZm8uRmllbGREYXRhT3pvWnZMcm1eA0tleZkFJGxpc3ReBVZhbHVlQAxFbnVtVHlwZU5hbWUuA25pbIYBQAdJc0RpcnR5hUAFVmFsdWUuBHR5cGWYDmI6YmFzZTY0QmluYXJ5CQFiIGh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hnoc8QXJyYXlPZnN0cmluZyB4bWxuczppPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vMjAwMy8xMC9TZXJpYWxpemF0aW9uL0FycmF5cyIgLz4BAQEBAZ8BAQEBAQEB</d4p1:Principal>
<d4p1:TypeName>Competition.Domain.Entities.SchoolSummaryList, Competition.Domain.Csla, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</d4p1:TypeName>
</request>
</Fetch>
</s:Body>
</s:Envelope>
I am trying to deserialize this to an object of type Csla.Server.Hosts.Silverlight.CriteriaRequest
The code for this class looks like this:
//-----------------------------------------------------------------------
// <copyright file="CriteriaRequest.cs" company="Marimer LLC">
// Copyright (c) Marimer LLC. All rights reserved.
// Website: http://www.lhotka.net/cslanet/
// </copyright>
// <summary>Message sent to the Silverlight</summary>
//-----------------------------------------------------------------------
using System;
using System.Runtime.Serialization;
using System.Security.Principal;
using Csla.Core;
namespace Csla.Server.Hosts.Silverlight
{
/// <summary>
/// Message sent to the Silverlight
/// WCF data portal.
/// </summary>
[DataContract]
public class CriteriaRequest
{
/// <summary>
/// Assembly qualified name of the
/// business object type to create.
/// </summary>
[DataMember]
public string TypeName { get; set; }
/// <summary>
/// Serialized data for the criteria object.
/// </summary>
[DataMember]
public byte[] CriteriaData { get; set; }
/// <summary>
/// Serialized data for the principal object.
/// </summary>
[DataMember]
public byte[] Principal { get; set; }
/// <summary>
/// Serialized data for the global context object.
/// </summary>
[DataMember]
public byte[] GlobalContext { get; set; }
/// <summary>
/// Serialized data for the client context object.
/// </summary>
[DataMember]
public byte[] ClientContext { get; set; }
/// <summary>
/// Serialized client culture.
/// </summary>
/// <value>The client culture.</value>
[DataMember]
public string ClientCulture { get; set; }
/// <summary>
/// Serialized client UI culture.
/// </summary>
/// <value>The client UI culture.</value>
[DataMember]
public string ClientUICulture { get; set; }
}
}
I have tried grabbing each of the three xml elements 'Body', 'Fetch' and 'request' from the soap message and storing them as an XElement variable named 'element' and running the code below:
DataContractSerializer dcs = new DataContractSerializer(typeof(CriteriaRequest));
MemoryStream ms = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(element.Value));
ms.Seek(0, SeekOrigin.Begin);
XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(ms, Encoding.UTF8, new XmlDictionaryReaderQuotas(), null);
CriteriaRequest fr = (CriteriaRequest)dcs.ReadObject(reader);
In every case it has failed to deserialize the object and I am out of ideas.
I think I have provided all the relavent information but if anything else would help please let me know and I will provide it.
Thanks.
You can't use the more simplistic serializers to directly serialize a CSLA .NET object graph. Serializers such as XmlSerializer, JsonSerializer, and the various DataContractSerializer variations aren't sophisticated enough to completely clone and object graph, and so they aren't supported by CSLA.
Only the BinaryFormatter, NetDataContractSerializer, and MobileFormatter are supported.
Odds are you are trying to expose your business objects directly as a service interface. That's a poor architectural choice, and one I'd never choose to support or encourage anyone to do. You might find the XML Services FAQ page useful.
I am attempting to upload a file through a Silverlight client using the following MessageContract:
[MessageContract]
public class CategoryClientFileTransferMC : IDisposable
{
/// <summary>
/// CategoryID - Category identity.
/// </summary>
[MessageHeader(MustUnderstand = true)]
public int CategoryID;
/// <summary>
/// ID - File identifier.
/// </summary>
[MessageHeader(MustUnderstand = true)]
public string ID;
/// <summary>
/// Length - File length in bytes.
/// </summary>
[MessageHeader(MustUnderstand = true)]
public long Length;
/// <summary>
/// FileByteStream - File stream.
/// </summary>
[MessageBodyMember(Order = 1)]
public Stream FileByteStream;
/// <summary>
/// Dispose the contract.
/// </summary>
public void Dispose()
{
if (FileByteStream != null)
{
FileByteStream.Close();
FileByteStream = null;
}
}
}
My problem is that the generated operation method on the client only takes a single argument; a byte array called FileByteStream. In other (non-Silverlight) clients I've created it asks for the MemberHeader fields as well. Without specifying these headers, the server has no idea what to do with the file. How can I set these headers when I call the operation?
Also, is there a better way to upload a file from a Silverlight client? This has been a huge headache.
Thanks.
The Silverlight subset of the WCF client does not support the [MessageHeader] attribute. You can still set message headers, but it's not as straightforward as in other platforms. Basically, you'll need to set the headers using the operation context, prior to making the call, like in the example below:
var client = new SilverlightReference1.MyClient();
using (new OperationContextScope(client.InnerChannel))
{
string contractNamespace = "http://tempuri.org/";
OperationContext.Current.OutgoingMessageHeaders.Add(
MessageHeader.CreateHeader("CategoryId", contractNamespace, 1));
OperationContext.Current.OutgoingMessageHeaders.Add(
MessageHeader.CreateHeader("ID", contractNamespace, "abc123"));
OperationContext.Current.OutgoingMessageHeaders.Add(
MessageHeader.CreateHeader("Length", contractNamespace, 123456L));
client.UploadFile(myFileContents);
}
Where contractNamespace is the XML namespace for the message header fields (IIRC they default to the same as the service contract). You can use Fiddler and something like the WCF Test Client to see which namespace is used there.