using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Xml;
using System.Xml.Schema;
///
/// This WCF behavior changes metadata generation for service contracts, so that operation
/// parameters are required by default (XML schema minOccurs="1").
/// See http://thorarin.net/blog/post.aspx?id=5fe3b4b6-0e3e-463e-ac42-10c1c4808853 for
/// a more thorough explanation.
///
///
/// Version 1.0 (2010-08-08):
/// - Original release
///
/// Version 1.1 (2011-04-14):
/// - Fixed a NullReferenceException that occurs when a service has two endpoints
/// configured that also use the same service contract. Thanks to Martin for reporting.
///
///
/// The OptionalAttribute can be used to mark individual parameters as optional.
///
/// [ServiceContract]
/// [RequiredParametersBehavior]
/// public interface IGreetingService
/// {
/// [OperationContract]
/// string Greet(string name, [Optional] string language);
/// }
///
///
[AttributeUsage(AttributeTargets.Interface)]
public class RequiredParametersBehaviorAttribute : Attribute, IContractBehavior, IWsdlExportExtension
{
private List _requiredParameter;
#region IContractBehavior Members (nothing to be done)
void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
}
void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
}
#endregion
#region IWsdlExportExtension Members
///
/// When ExportContract is called to generate the necessary metadata, we inspect the service
/// contract and build a list of parameters that we'll need to adjust the XSD for later.
///
void IWsdlExportExtension.ExportContract(WsdlExporter exporter, WsdlContractConversionContext context)
{
_requiredParameter = new List();
foreach (var operation in context.Contract.Operations)
{
var inputMessage = operation.Messages.Where(m => m.Direction == MessageDirection.Input).First();
var parameters = operation.SyncMethod.GetParameters();
Debug.Assert(parameters.Length == inputMessage.Body.Parts.Count);
for (int i = 0; i < parameters.Length; i++)
{
object[] attributes = parameters[i].GetCustomAttributes(typeof(OptionalAttribute), false);
if (attributes.Length == 0)
{
// The parameter has no [Optional] attribute, add it to the list of parameters
// that we need to adjust the XML schema for later on.
_requiredParameter.Add(new RequiredMessagePart()
{
Namespace = inputMessage.Body.Parts[i].Namespace,
Message = operation.Name,
Name = inputMessage.Body.Parts[i].Name
});
}
}
}
}
///
/// When ExportEndpoint is called, the XML schemas have been generated. Now we can manipulate to
/// our heart's content.
///
void IWsdlExportExtension.ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
{
if (_requiredParameter == null)
{
// If we have defined two endpoints implementing the same contract within the same service,
// this method will be called twice. We only need to modify the schema once however.
return;
}
foreach (var p in _requiredParameter)
{
var schemas = exporter.GeneratedXmlSchemas.Schemas(p.Namespace);
foreach (XmlSchema schema in schemas)
{
var message = (XmlSchemaElement)schema.Elements[p.XmlQualifiedName];
var complexType = message.ElementSchemaType as XmlSchemaComplexType;
Debug.Assert(complexType != null, "Expected input message to be complex type.");
var sequence = complexType.Particle as XmlSchemaSequence;
Debug.Assert(sequence != null, "Expected a sequence.");
foreach (XmlSchemaElement item in sequence.Items)
{
if (item.Name == p.Name)
{
item.MinOccurs = 1;
item.MinOccursString = "1";
break;
}
}
}
}
// Throw away the temporary list we generated
_requiredParameter = null;
}
#endregion
#region Nested types
private class RequiredMessagePart
{
public string Namespace { get; set; }
public string Message { get; set; }
public string Name { get; set; }
public XmlQualifiedName XmlQualifiedName
{
get
{
return new XmlQualifiedName(Message, Namespace);
}
}
}
#endregion
}
[AttributeUsage(AttributeTargets.Parameter)]
public class OptionalAttribute : Attribute
{
}