Can I associate a connection string with an endpoint in WCF? - wcf

We have a WCF REST service that connects to a database. In fact, we have several instances of the database, all with the same schema.
We would like to set up one endpoint for each database instance and associate a connection string with the endpoint. The service would read the connection string and connect to the appropriate SQL Server instance.
I'm sure this is possible; is it a good idea? How do I set it up? Is there documentation on MSDN?
Edit: I found this question, where the answer suggests adding connection information on the client in a header. I don't want to do that—for security reasons, and because I do want to have a distinct uri for each database.

This was a bit harder than I thought. WCF has so many extensibility points its hard to pick the right one. Please answer or comment if you think there's a better way, or anything wrong with this.
I've settled on using a custom class that implements IEndpointBehavior and IDispatchMessageInspector. I have a class derived from BehaviorExtensionElement that lets me associate the behavior with an endpoint in configuration. This blog post describes hot do do that.
My DatabaseConnectionContext class looks like this:
/// <summary>
/// An endpoint behavior that associates a database connection string name with the endpoint and adds it to the
/// properties of incoming messages.
/// </summary>
public class DatabaseConnectionContext : IEndpointBehavior, IDispatchMessageInspector
{
/// <summary>
/// Initializes a new instance of the <see cref="DatabaseConnectionContext"/> class with the provided connection string name.
/// </summary>
/// <param name="connectionStringName">The name of the connection string to associate with the endpoint.</param>
public DatabaseConnectionContext(string connectionStringName)
{
this.ConnectionStringName = connectionStringName;
}
/// <summary>
/// Gets the name of the connection string to associate with the endpoint.
/// </summary>
public string ConnectionStringName { get; private set; }
/// <inheritdoc />
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
/// <inheritdoc />
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
}
/// <inheritdoc />
public void Validate(ServiceEndpoint endpoint)
{
}
/// <inheritdoc />
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
request.Properties["connectionStringName"] = this.ConnectionStringName;
return null;
}
/// <inheritdoc />
public void BeforeSendReply(ref Message reply, object correlationState)
{
}
}
In my service class I have this method:
/// <summary>
/// Returns the connection string to use for this service call.
/// </summary>
/// <returns>A SQL Server database connection string.</returns>
private string GetConnectionString()
{
string connectionStringName = (string)OperationContext.Current.IncomingMessageProperties["connectionStringName"];
return ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
}
My BehaviorExtensionElement class looks like this:
/// <summary>
/// Associates a <see cref="DatabaseConnectionContext"/> with an endpoint in configuration.
/// </summary>
public class DatabaseConnectionContextBehaviorExtension : BehaviorExtensionElement
{
/// <summary>
/// The name of the <see cref="ConnectionStringName"/> property when it appears in a configuration file.
/// </summary>
private const string ConnectionStringNamePropertyName = "connectionStringName";
/// <summary>
/// Gets or sets the name of the configuration string to associate with the endpoint.
/// </summary>
[ConfigurationProperty(ConnectionStringNamePropertyName)]
public string ConnectionStringName
{
get
{
return (string)this[ConnectionStringNamePropertyName];
}
set
{
this[ConnectionStringNamePropertyName] = value;
}
}
/// <inheritdoc />
public override Type BehaviorType
{
get { return typeof(DatabaseConnectionContext); }
}
/// <inheritdoc />
protected override object CreateBehavior()
{
return new DatabaseConnectionContext(this.ConnectionStringName);
}
}
My web.config contains something like this:
<behaviors>
<endpointBehaviors>
<behavior name="DevRestEndpointConfiguration">
<webHttp helpEnabled="false" />
<connectionStringInterceptor connectionStringName="myDevConnectionStringName" />
</behavior>
<behavior name="ProductionRestEndpointConfiguration">
<webHttp helpEnabled="false" />
<connectionStringInterceptor connectionStringName="myProductionConnectionStringName" />
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="connectionStringInterceptor" type="DatabaseConnectionContextBehaviorExtension, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
Each <endpoint /> element in the <services /> section has its behaviorConfiguration set to the name of an appropriate element from the <endpointBehaviors /> section.

why don't you add a new parameters specifying what is the database the call will connect?
for example:
you can add a db parameters that will get a number and from there you will connect
you can add such parameter on the authentication method
as the example for the first item:
public ProductItem GetProduct(int productId, int db = 1)
{
ProductItem product = new ProductItem();
string connectionString = getConnectionStringForDb(db);
using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlCommand command = new SqlCommand("SELECT name, price FROM Products WHERE productId = #product;", connection);
command.Parameters.AddWithValue("#product", productId);
try
{
connection.Open();
SqlDataReader reader = command.ExecuteReader();
reader.Read();
product = new product({
Name = reader[0],
Price = reader[1]
});
reader.Close();
}
catch (Exception ex)
{
// Log exception
}
}
return product;
}
taken from MSDN
private string getConnectionStringForDb(int type)
{
System.Configuration.ConnectionStringSettings connString;
System.Configuration.Configuration rootWebConfig = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("/MyWebSiteRoot");
if (rootWebConfig.ConnectionStrings.ConnectionStrings.Count > 0) {
connString = rootWebConfig.ConnectionStrings.ConnectionStrings["DBConnectionString_" + type];
if (connString == null) {
// LOG ERROR
}
}
return connString.ConnectionString;
}
and just add your connection strings in your web.config and name then like:
DBConnectionString_1, DBConnectionString_2, DBConnectionString_3
or anything that makes sense to you.
<connectionStrings>
<add
name="DBConnectionString_1"
connectionString="Data Source=serverName;Initial
Catalog=Northwind;Persist Security Info=True;User
ID=userName;Password=password"
providerName="System.Data.SqlClient"
/>
<add
name="DBConnectionString_2"
connectionString="Data Source=serverName;Initial
Catalog=Northwind;Persist Security Info=True;User
ID=userName;Password=password"
providerName="System.Data.SqlClient"
/>
<add
name="DBConnectionString_3"
connectionString="Data Source=serverName;Initial
Catalog=Northwind;Persist Security Info=True;User
ID=userName;Password=password"
providerName="System.Data.SqlClient"
/>
</connectionStrings>

With this in your web.config:
<configuration>
<appSettings>
<add key="Foo.svc" value="tagvalue1"/>
</appSettings>
...
You could retrieve the value at runtime this way:
private static string GetConfigValue()
{
ServiceEndpointCollection ec = OperationContext.Current
.Host.Description.Endpoints;
if (ec!=null)
{
var segments = ec[0].Address.ToString().Split('/');
var s = segments[segments.Length-1]; // "Foo.svc"
return ConfigurationManager.AppSettings[s]; // "tagvalue1"
}
return null;
}

Related

WCF: Getting (400) Bad Request when sending small amount of data

I am getting "The remote server returned an unexpected response: (400) Bad Request" when I try to send more than 100 audit entries. I am using Fiddler to help debug and do see the request being sent to the server. The client and server both use the same interface.
[ServiceContract]
public interface ISyncDataContract
{
#region Audit Log
/// <summary>
/// Creates a collection of new audit entries items in the database.
/// </summary>
/// <param name="items">The audit entry items to be created.</param>
/// <returns><c>True</c> if created successfully; otherwise, <c>false</c>.</returns>
[OperationContract]
[WebInvoke(UriTemplate = "AuditEntries", Method = "PUT")]
bool CreateAuditEntryItems(AuditEntryItemCollection items);
/// <summary>
/// Gets all the audit entry items available.
/// </summary>
/// <returns>An <see cref="AuditEntryItemCollection"/> object containing all the
/// available audit entry items.</returns>
[OperationContract]
[WebGet(UriTemplate = "AuditEntries")]
Message GetAuditEntryItems();
#endregion
}
AuditEntryItem.cs
[DataContract]
public class AuditEntryItem
{
#region Constructor/Deconstructor
/// <summary>
/// Initializes a new instance of the <see cref="AuditEntryItem"/> class.
/// </summary>
public AuditEntryItem()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AuditEntryItem"/> class.
/// </summary>
/// <param name="auditEntry">The audit entry.</param>
public AuditEntryItem(AuditEntry auditEntry)
{
if (auditEntry == null)
{
throw new ArgumentNullException("auditEntry");
}
this.Audit_Type = auditEntry.Audit_type;
this.ComputerName = Environment.MachineName;
this.Message = auditEntry.Message;
this.Sequence_Number = auditEntry.Sequence_number;
this.Session_ID = auditEntry.Session_ID;
this.SyncDate = DateTime.Now;
this.Time_Stamp = auditEntry.Time_stamp;
this.User_ID = auditEntry.User_ID;
}
#endregion Constructor/Deconstructor
#region Properties
/// <summary>
/// Gets or sets the session ID.
/// </summary>
/// <value>
/// The session ID.
/// </value>
[DataMember]
[XmlElement(ElementName = #"Session_ID")]
public string Session_ID { get; set; }
/// <summary>
/// Gets or sets the user ID.
/// </summary>
/// <value>
/// The user ID.
/// </value>
[DataMember]
[XmlElement(ElementName = #"User_ID")]
public string User_ID { get; set; }
/// <summary>
/// Gets or sets the time stamp.
/// </summary>
/// <value>
/// The time stamp.
/// </value>
[DataMember]
[XmlElement(ElementName = #"Time_Stamp")]
public string Time_Stamp { get; set; }
/// <summary>
/// Gets or sets the sequence number.
/// </summary>
/// <value>
/// The sequence number.
/// </value>
[DataMember]
[XmlElement(ElementName = #"Sequence_number")]
public int Sequence_Number { get; set; }
/// <summary>
/// Gets or sets the message.
/// </summary>
/// <value>
/// The message.
/// </value>
[DataMember]
[XmlElement(ElementName = #"Message")]
public string Message { get; set; }
/// <summary>
/// Gets or sets the type of the audit.
/// </summary>
/// <value>
/// The type of the audit.
/// </value>
[DataMember]
[XmlElement(ElementName = #"Audit_type")]
public string Audit_Type { get; set; }
/// <summary>
/// Gets or sets the name of the computer.
/// </summary>
/// <value>
/// The name of the computer.
/// </value>
[DataMember]
[XmlElement(ElementName = #"ComputerName")]
public string ComputerName { get; set; }
/// <summary>
/// Gets or sets the sync date.
/// </summary>
/// <value>
/// The sync date.
/// </value>
[DataMember]
[XmlElement(ElementName = #"SyncDate")]
public DateTime? SyncDate { get; set; }
/// <summary>
/// Gets the time stamp value in a date time format.
/// </summary>
[XmlIgnore]
public DateTime DisplayTimeStamp
{
get { return this.TimeStampDateTime(); }
}
#endregion Properties
#region Overrides
public override bool Equals(object obj)
{
return obj is AuditEntryItem ? this.Equals((AuditEntryItem)obj) : false;
}
public bool Equals(AuditEntryItem other)
{
if (ReferenceEquals(this, other))
{
return true;
}
return string.Equals(this.Audit_Type, other.Audit_Type) &&
string.Equals(this.ComputerName, other.ComputerName) &&
string.Equals(this.Message, other.Message) &&
this.Sequence_Number == other.Sequence_Number &&
string.Equals(this.Session_ID, other.Session_ID) &&
this.SyncDate == other.SyncDate &&
string.Equals(this.Time_Stamp, other.Time_Stamp) &&
string.Equals(this.User_ID, other.User_ID);
}
public override int GetHashCode()
{
unchecked
{
var result = (this.Audit_Type != null ? this.Audit_Type.GetHashCode() : 0);
result = (result * 397) ^ (this.ComputerName != null ? this.ComputerName.GetHashCode() : 0);
result = (result * 397) ^ (this.Message != null ? this.Message.GetHashCode() : 0);
result = (result * 397) ^ this.Sequence_Number.GetHashCode();
result = (result * 397) ^ (this.Session_ID != null ? this.Session_ID.GetHashCode() : 0);
result = (result * 397) ^ (this.SyncDate != null ? this.SyncDate.GetHashCode() : 0);
result = (result * 397) ^ (this.Time_Stamp != null ? this.Time_Stamp.GetHashCode() : 0);
result = (result * 397) ^ (this.User_ID != null ? this.User_ID.GetHashCode() : 0);
return result;
}
}
#endregion Overrides
/// <summary>
/// Converts the Java time stamp value into a readable format.
/// </summary>
/// <returns>A readable date time format.</returns>
private DateTime TimeStampDateTime()
{
if (this.Time_Stamp.IsNullOrEmpty())
{
return new DateTime(1970, 01, 01);
}
long value;
if (!long.TryParse(this.Time_Stamp, out value))
{
return new DateTime(1970, 01, 01);
}
value = value / 1000;
return new DateTime(1970, 01, 01).AddSeconds(value);
}
}
AuditEntryItemCollection.cs
[DataContract]
[XmlRoot(ElementName = "AuditLog")]
public class AuditEntryItemCollection
{
#region Declarations
#endregion Declarations
#region Constructor/Deconstructor
/// <summary>
/// Initializes a new instance of the <see cref="AuditEntryItemCollection"/> class.
/// </summary>
public AuditEntryItemCollection()
{
this.AuditEntryItems = new List<AuditEntryItem>();
}
#endregion Constructor/Deconstructor
#region Properties
/// <summary>
/// Gets or sets the collection of <see cref="AuditEntryItem"/>
/// objects.
/// </summary>
/// <value>
/// The collection of <see cref="AuditEntryItem"/> objects.
/// </value>
[XmlElement(ElementName = #"AuditEntry")]
[DataMember]
public List<AuditEntryItem> AuditEntryItems { get; set; }
#endregion Properties
}
App.config
<?xml version="1.0" encoding="utf-8" ?>
<behaviors>
<endpointBehaviors>
<behavior name="restXmlBehavior">
<webHttp helpEnabled="true" defaultOutgoingResponseFormat="Xml" />
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
</behavior>
<behavior name="rssAtomBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="metadataBehavior" >
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="StreamedHttp"
maxReceivedMessageSize="2147483647"
transferMode="Streamed" >
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="6000000" />
</binding>
</webHttpBinding>
</bindings>
UPDATE:
The error I am now getting is "The maximum message size quota for incoming messages (65536) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element." As far as I can tell, I have already done this. Is there some other setting I need to set?
You could make sure the httpRuntime is also configured for large requests:
(An example grabbed from a service I use to upload files:)
<httpRuntime executionTimeout="3600" maxRequestLength="50000000" maxQueryStringLength="2097151" requestValidationMode="2.0" />
And also perhaps look at the buffer pool sizes on the binding (again, these values are just examples):
<binding name="WHB" maxReceivedMessageSize="50000000" maxBufferPoolSize="50000000" crossDomainScriptAccessEnabled="true">
<readerQuotas maxArrayLength="50000000" maxStringContentLength="50000000" />

Protobuf net not serializing when using WCF

I am trying to use protobuf to serialize my WCF calls, but it seems the object is not getting serialized by the client. Some things to note:
I am using a shared DTO library.
I am using a ChannelFactory to
invoke the service (so the types are not losing their datamember
attributes).
I can serialize and deserialize the objects just using
normal protobuf.net code, so the types themselves seem to be ok
I am using version 2.0.0.480 of protobuf.net
I haven't posted the service code as the problem is with the outgoing message (message log posted below)
The client and service work fine if I don't use the protobuf endpoint behaviour.
My main method looks as follows
static void Main(string[] args)
{
Base.PrepareMetaDataForSerialization();
FactoryHelper.InitialiseFactoryHelper(new ServiceModule());
Member m = new Member();
m.FirstName = "Mike";
m.LastName = "Hanrahan";
m.UserId = Guid.NewGuid();
m.AccountStatus = MemberAccountStatus.Blocked;
m.EnteredBy = "qwertt";
ChannelFactory<IMembershipService> factory = new ChannelFactory<IMembershipService>("NetTcpBinding_MembershipService");
var client = factory.CreateChannel();
using (var ms = new MemoryStream())
{
Serializer.Serialize<Member>(ms, m);
Console.WriteLine(ms.Length.ToString());
ms.Position = 0;
var member2 = Serializer.Deserialize<Member>(ms);
Console.WriteLine(member2.EnteredBy);
Console.WriteLine(member2.FirstName);
}
var result = client.IsMemberAllowedToPurchase(m);
System.Console.Write(result.IsValid.ToString());
factory.Close();
var input = Console.ReadLine();
}
My client configuration looks as follows:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
</configSections>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_Common" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"
hostNameComparisonMode="StrongWildcard" listenBacklog="10" maxBufferPoolSize="524288"
maxBufferSize="1000065536" maxConnections="10" maxReceivedMessageSize="1000000">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Transport">
<transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
<message clientCredentialType="Windows" />
</security>
</binding>
</netTcpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="Proto.Common.EndpointBehavior">
<protobuf />
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="protobuf" type="ProtoBuf.ServiceModel.ProtoBehaviorExtension, protobuf-net, Version=2.0.0.480, Culture=neutral, PublicKeyToken=257b51d87d2e4d67" />
</behaviorExtensions>
</extensions>
<client>
<endpoint address="net.tcp://mikes-pc:12002/MembershipService.svc"
behaviorConfiguration="Proto.Common.EndpointBehavior" binding="netTcpBinding"
bindingConfiguration="NetTcpBinding_Common" contract="PricesForMe.Core.Entities.ServiceInterfaces.IMembershipService"
name="NetTcpBinding_MembershipService">
<identity>
<userPrincipalName value="Mikes-PC\Mike" />
</identity>
</endpoint>
</client>
<diagnostics>
<messageLogging
logEntireMessage="true"
logMalformedMessages="true"
logMessagesAtServiceLevel="true"
logMessagesAtTransportLevel="true"
maxMessagesToLog="3000"
maxSizeOfMessageToLog="2000"/>
</diagnostics>
</system.serviceModel>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
</startup>
<system.diagnostics>
<sources>
<source name="System.ServiceModel"
switchValue="Information, ActivityTracing"
propagateActivity="true">
<listeners>
<add name="traceListener"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="E:\Work\Logs\IMembershipServiceWcfTrace_Client.svclog" />
</listeners>
</source>
<source name="System.ServiceModel.MessageLogging">
<listeners>
<add name="messages"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="E:\Work\Logs\IMembershipServiceWcfTrace_Client_messages.svclog" />
</listeners>
</source>
</sources>
</system.diagnostics>
</configuration>
After logging the client message, I get the following entry in the log
<MessageLogTraceRecord>
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://www.pricesforme.com/services/MembershipService/IsMemberAllowedToPurchase</a:Action>
<a:MessageID>urn:uuid:8b545576-c453-4be6-8d5c-9913e2cca4bf</a:MessageID>
<ActivityId CorrelationId="b4e9361f-1fbc-4b2d-b7ee-fb493847998a" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">6d712899-62fd-4547-9517-e9de452305c6</ActivityId>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink"></VsDebuggerCausalityData>
</s:Header>
<s:Body>
<IsMemberAllowedToPurchase xmlns="http://www.pricesforme.com/services/">
<proto></proto>
</IsMemberAllowedToPurchase>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>
So as can been seen in the above log message the proto entry has no data in it. My member class looks as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using PricesForMe.Core.Entities.Common;
using PricesForMe.Core.Entities.Ordering;
namespace PricesForMe.Core.Entities.Members
{
/// <summary>
/// This entity represents a member or user of the site.
/// </summary>
[DataContract]
[Serializable]
public class Member: User
{
public Member()
:base()
{
EntityType = Entities.EntityType.Member;
}
[DataMember(Order = 20)]
public int Id { get; set; }
[DataMember(Order = 21)]
public string MemberName { get; set; }
[DataMember(Order = 22)]
public PaymentInfo DefaultPaymentMethod { get; set; }
[DataMember(Order = 23)]
public MemberAccountStatus AccountStatus { get; set; }
#region static
public static readonly string CacheCollectionKey = "MemberCollection";
private static readonly string CacheItemKeyPrefix = "Member:";
public static string GetCacheItemKey(int id)
{
return CacheItemKeyPrefix + id.ToString();
}
#endregion
}
}
The parent user class looks as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Diagnostics.Contracts;
namespace PricesForMe.Core.Entities.Common
{
/// <summary>
/// This class represents a user in the system. For example, a user could be a member or a merchant user.
/// </summary>
[DataContract]
[Serializable]
public class User: Base
{
public User()
:base()
{
EntityType = Entities.EntityType.User;
}
[DataMember(Order = 10)]
public Guid UserId { get; set; }
[DataMember(Order = 11, Name = "First Name")]
public string FirstName { get; set; }
[DataMember(Order = 12, Name = "Last Name")]
public string LastName { get; set; }
}
}
}
And the base class looks as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Diagnostics.Contracts;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using ProtoBuf.Meta;
namespace PricesForMe.Core.Entities
{
/// <summary>
/// This is the base class for all entities involved in the request/response pattern of our services
/// </summary>
/// <remarks>
/// The objects derived from this class are used to transfer data from our service classes to our UIs and back again and they should
/// not contain any logic.
/// </remarks>
[DataContract]
[Serializable]
public abstract class Base
{
public Base()
{
//Set some defaults for this
EnteredBy = System.Environment.UserName;
EnteredSource = System.Environment.MachineName;
}
/// <summary>
/// This is the record timestamp
/// </summary>
[DataMember(Order = 2)]
public DateTime RecordTimeStamp { get; set; }
/// <summary>
/// This is the name of the user who last edited the entity
/// </summary>
[DataMember(Order = 3)]
public string EnteredBy { get; set; }
/// <summary>
/// This is the source of the last edited entity
/// </summary>
[DataMember(Order = 4)]
public string EnteredSource { get; set; }
[DataMember(Order = 5)]
private PricesForMe.Core.Entities.Common.ValidationResult _validationResult = null;
/// <summary>
/// Data on the validity of the entity.
/// </summary>
public PricesForMe.Core.Entities.Common.ValidationResult ValidationData
{
get
{
_validationResult = Validate();
return _validationResult;
}
set
{
_validationResult = value;
}
}
/// <summary>
/// Flag denoting if the record is a new record or not.
/// </summary>
/// <remarks>
/// To flag an entity as an existing record call the "FlagAsExistingReport()" method.
/// </remarks>
public bool IsNewRecord
{
get
{
return _isNewRecord;
}
}
[DataMember(Order = 6)]
protected bool _isNewRecord = true;
/// <summary>
/// Flags the entity as a record that already exists in the database
/// </summary>
/// <remarks>
/// This is a method rather than a field to demonstrait that this should be called with caution (as opposed to inadvertantly setting a flag!)
/// <para>
/// Note that this method should only need to be called on object creation if the entity has a composite key. Otherwise the flag is
/// set when the id is being set. It should always be called on saving an entity.
/// </para>
/// </remarks>
public void FlagAsExistingRecord()
{
_isNewRecord = false;
}
public virtual PricesForMe.Core.Entities.Common.ValidationResult Validate()
{
if (_validationResult == null)
{
_validationResult = new PricesForMe.Core.Entities.Common.ValidationResult();
_validationResult.MemberValidations = new List<Common.ValidationResult>();
_validationResult.RulesViolated = new List<Common.ValidationRule>();
}
return _validationResult;
}
/// <summary>
/// This is the type of entity we are working with
/// </summary>
[DataMember(Order = 7)]
private EntityType _entityType = EntityType.Unknown;
public EntityType EntityType
{
get
{
return _entityType;
}
protected set
{
_entityType = value;
}
}
/// <summary>
/// Flag to say if the id generated for this class need to be int64 in size.
/// </summary>
[DataMember(Order = 9)]
public bool IdRequiresInt64 { get; protected set; }
/// <summary>
/// This method tells us if the database id has been assigned. Note that this does
/// not mean the entity has been saved, only if the id has been assigned (so the id could be greater than 0, but the
/// entity could still be a NewRecord
/// </summary>
/// <returns></returns>
[DataMember(Order = 8)]
public bool HasDbIdBeenAssigned { get; protected set; }
private Guid _validationId = Guid.NewGuid();
public Guid EntityValidationId
{
get
{
return _validationId;
}
}
/// <summary>
/// Converts an object into another type of object based on the mapper class provided.
/// </summary>
/// <remarks>
/// This method allows us to easily convert between objects without concerning ourselves with the mapping implementation. This
/// allows us to use various mapping frameworks (e.g. Automapper, ValueInjector) or create our own custom mapping.
/// </remarks>
/// <typeparam name="TDestination">The type we want to convert to</typeparam>
/// <typeparam name="KMapper">The mapping type</typeparam>
/// <returns>The new type</returns>
public TDestination ConvertTo<TDestination, TSource, KMapper>()
where KMapper : IEntityMapper<TDestination, TSource>
where TSource : class
{
return Base.ConvertToItem<TDestination, TSource, KMapper>(this as TSource);
}
/// <summary>
/// Returns all known child types
/// </summary>
public IEnumerable<Type> GetAllTypes()
{
Assembly current = Assembly.GetCallingAssembly();
List<Type> derivedTypes = new List<Type>();
var allTypes = current.GetTypes();
foreach (var t in allTypes)
{
if (t.IsAssignableFrom(typeof(Base)))
{
derivedTypes.Add(t);
}
}
return derivedTypes;
}
#region Static Methods
/// <summary>
/// Converts a list of one type to a list of another type
/// </summary>
/// <typeparam name="TDestination">The type we want to convert to</typeparam>
/// <typeparam name="TSource">The source type</typeparam>
/// <typeparam name="KMapper">The mapper class</typeparam>
/// <param name="source">The source list of items.</param>
/// <returns></returns>
public static List<TDestination> ConvertToList<TDestination, TSource, KMapper>(IEnumerable<TSource> source)
where KMapper : IEntityMapper<TDestination, TSource>
where TSource : class
{
List<TDestination> result = new List<TDestination>();
KMapper mapper = Activator.CreateInstance<KMapper>();
foreach (var item in source)
{
result.Add(mapper.Convert(item));
}
return result;
}
public static TDestination ConvertToItem<TDestination, TSource, KMapper>(TSource source)
where KMapper : IEntityMapper<TDestination, TSource>
where TSource : class
{
//Return default (i.e. null for ref objects) if the source is null.
if (source == null) { return default(TDestination); }
KMapper mapper = Activator.CreateInstance<KMapper>();
return mapper.Convert(source);
}
private static object _metaLock = new object();
private static bool _metaDataPrepared = false;
/// <summary>
/// Creates protobuf type models from the entities in this assembly
/// </summary>
public static void PrepareMetaDataForSerialization()
{
lock (_metaLock)
{
if (_metaDataPrepared) { return; }
Assembly current = Assembly.GetExecutingAssembly();
var allTypes = current.GetTypes();
foreach (var t in allTypes)
{
checkType(t);
}
}
}
private static void checkType(Type type)
{
Assembly current = Assembly.GetExecutingAssembly();
var allTypes = current.GetTypes();
int key = 1000;
foreach (var t in allTypes)
{
if (t.IsSubclassOf(type) && t.BaseType == type)
{
RuntimeTypeModel.Default[type].AddSubType(key, t);
key++;
}
}
}
#endregion
}
}
The PrepareMetaDataForSerialization method on base configures the RuntimeModel for protobuf.net, but I mentioned earlier, the serialization and deserialization works fine outside of the WCF, so I think the DTOs are ok. Any ideas around what could be causing the problem are greatly appreciated.
k; the element name looks correct (proto, matching XmlProtoSerializer.PROTO_ELEMENT), so protobuf-net definitely tried to do something. It also doesn't include #nil to represent null, so it knows there was data. Beyond that, it serializes the object to a MemoryStream and writes it as base-64 (identical to how byte[] etc is represented, which allows WCF to hoist the data silently and automatically if things like MTOM are enabled). So the question becomes "why would my type serialize to nothing?"
The use of DataContract/DataMember is fine, and matches my existing WCF integration tests.
I wonder how much of this is due to the inheritance (only one of the members shown relates to the concrete type, and I would hazard a guess that Blocked is 0, which has particular handling).
I cannot, however, emphasise enough how unsafe your current inheritance handling is; the numbers matter, and reflection makes no guarantees re order. I would strongly suggest you revisit this and make inheritance numbering more predicatable.
Very minor observation, but there is no need to store EntityType - it is entirey redundant and could be handled via polymorphism with no need for storage.
Also, there's an important bug that _metaDataPrepared is never set to true.
However! Ultimately I cannot reproduce this; I have used your code (or most of it) to generate an integration test, and - it passes; meaning: using WCF, NetTcpBinding, your classes (including your inheritance fixup code), and the protobuf packing, it just works. The data that goes over the wire is the data we expect.
Happy to try to help further, but I need to be able to repro it... and right now, I can't.
The first thing I'd do is add the missing _metaDataPrepared = true; to see if that helps.
I thought you need to use ProtoContract instead of DataContract per this SO? Also, make sure you set "Reuse types in referenced assemblies" when configuring service reference. And according to this SO they support data contracts but you have to have the order set [DataMember(Order = 0)] (at least that's what worked for me).

Declaratively configure WCF behavior extension on programmatically constructed endpoint

I have a WCF behavior extension that I would like to add to a WCF client. However, the client is constructed programmatically. The endpoint address may vary, but I know the type. I could add the behavior programmatically or in the config file (preferred), but I need to pass some configuration in the config file only.
I don't want this in Common behaviors (machine.config).
I can add the behavior programmatically
endpoint.Behaviors.Add(new MyCustomBehavior())
But I'd rather do it in config, so I can configure the extension there as well.
Is it possible to declaratively add and configure an endpoint behavior extension to a programmatically constructed endpoint knowing only the the type or interface while leaving the client endpoint to be constructed programmatically?
<system.serviceModel>
<client>
<!-- Created programmatically -->
</client>
<extensions>
<behaviorExtensions>
<add name="MyCustomBehavior" type="namespace.CustomBehaviors", MyAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="MyCustomBehavior">
<MyCustomBehavior MyImportantBehaviorParam1="foo" />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Of course I could put the config in another section, and have my behavior read it there, but I'd rather use the WCF facilities if possible.
To do that you need to create a behavior configuration extension for your endpoint. For more information on how to do that, check https://learn.microsoft.com/en-us/archive/blogs/carlosfigueira/wcf-extensibility-behavior-configuration-extensions.
Update: I see your issue now. There's no direct way to add to an endpoint created via code a behavior declared in configuration. You can still do it, though, but you'll need to use some reflection to access the CreateBehavior method of the behavior configuration extension (the method is protected) to actually create the endpoint behavior to add it to the endpoint created via code. The code below shows how this can be done.
public class StackOverflow_10232385
{
public class MyCustomBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
Console.WriteLine("In {0}.{1}", this.GetType().Name, MethodBase.GetCurrentMethod().Name);
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
Console.WriteLine("In {0}.{1}", this.GetType().Name, MethodBase.GetCurrentMethod().Name);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
Console.WriteLine("In {0}.{1}", this.GetType().Name, MethodBase.GetCurrentMethod().Name);
}
public void Validate(ServiceEndpoint endpoint)
{
Console.WriteLine("In {0}.{1}", this.GetType().Name, MethodBase.GetCurrentMethod().Name);
}
}
public class MyCustomBehaviorExtension : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(MyCustomBehavior); }
}
protected override object CreateBehavior()
{
return new MyCustomBehavior();
}
}
[ServiceContract]
public interface ITest
{
[OperationContract]
string Echo(string text);
}
public class Service : ITest
{
public string Echo(string text)
{
return text;
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");
var configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
ServiceModelSectionGroup smsg = configuration.GetSectionGroup("system.serviceModel") as ServiceModelSectionGroup;
EndpointBehaviorElement endpointBehaviorElement = smsg.Behaviors.EndpointBehaviors["MyCustomBehavior_10232385"];
foreach (BehaviorExtensionElement behaviorElement in endpointBehaviorElement)
{
MethodInfo createBehaviorMethod = behaviorElement.GetType().GetMethod("CreateBehavior", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, Type.EmptyTypes, null);
IEndpointBehavior behavior = createBehaviorMethod.Invoke(behaviorElement, new object[0]) as IEndpointBehavior;
endpoint.Behaviors.Add(behavior);
}
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new BasicHttpBinding(), new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.Echo("Hello"));
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
And the configuration for this code:
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="myCustomBehavior_10232385" type="QuickCode1.StackOverflow_10232385+MyCustomBehaviorExtension, QuickCode1"/>
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="MyCustomBehavior_10232385">
<myCustomBehavior_10232385/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>

Errors and Restful WCF

Hi I'm creating a Restful WCF web services that serves up JSON and XML. I believe with wcf 4 you can specify an error object which will return a detailed error to the client in JSON. Is there any way of completely overriding this and return text and therefore have complete control over the format of the error?
Yes you have.
You can create custom error handler and do what you feel like.
See the attached code (just change the JsonErrorDetails class as needed).
That's the custom error handler:
public class JsonErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
// Yes, we handled this exception...
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
// Create message
var jsonError = new JsonErrorDetails { Message = error.Message, ExceptionType = error.GetType().FullName };
fault = Message.CreateMessage(version, "", jsonError,
new DataContractJsonSerializer(typeof(JsonErrorDetails)));
// Tell WCF to use JSON encoding rather than default XML
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
// Modify response
var rmp = new HttpResponseMessageProperty
{
StatusCode = HttpStatusCode.BadRequest,
StatusDescription = "Bad Request",
};
rmp.Headers[HttpResponseHeader.ContentType] = "application/json";
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
}
}
That's an extended service behavior to inject the error handler:
/// <summary>
/// This class is a custom implementation of the WebHttpBehavior.
/// The main of this class is to handle exception and to serialize those as requests that will be understood by the web application.
/// </summary>
public class ExtendedWebHttpBehavior : WebHttpBehavior
{
protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
// clear default erro handlers.
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
// add our own error handler.
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new JsonErrorHandler());
//BehaviorExtensionElement
}
}
That's a custom binding so you'll be able to configure it in the web.config
/// <summary>
/// Enables the ExtendedWebHttpBehavior for an endpoint through configuration.
/// Note: Since the ExtendedWebHttpBehavior is derived of the WebHttpBehavior we wanted to have the exact same configuration.
/// However during the coding we've relized that the WebHttpElement is sealed so we've grabbed its code using reflector and
/// modified it to our needs.
/// </summary>
public sealed class ExtendedWebHttpElement : BehaviorExtensionElement
{
private ConfigurationPropertyCollection properties;
/// <summary>Gets or sets a value that indicates whether help is enabled.</summary>
/// <returns>true if help is enabled; otherwise, false. </returns>
[ConfigurationProperty("helpEnabled")]
public bool HelpEnabled
{
get
{
return (bool)base["helpEnabled"];
}
set
{
base["helpEnabled"] = value;
}
}
/// <summary>Gets and sets the default message body style.</summary>
/// <returns>One of the values defined in the <see cref="T:System.ServiceModel.Web.WebMessageBodyStyle" /> enumeration.</returns>
[ConfigurationProperty("defaultBodyStyle")]
public WebMessageBodyStyle DefaultBodyStyle
{
get
{
return (WebMessageBodyStyle)base["defaultBodyStyle"];
}
set
{
base["defaultBodyStyle"] = value;
}
}
/// <summary>Gets and sets the default outgoing response format.</summary>
/// <returns>One of the values defined in the <see cref="T:System.ServiceModel.Web.WebMessageFormat" /> enumeration.</returns>
[ConfigurationProperty("defaultOutgoingResponseFormat")]
public WebMessageFormat DefaultOutgoingResponseFormat
{
get
{
return (WebMessageFormat)base["defaultOutgoingResponseFormat"];
}
set
{
base["defaultOutgoingResponseFormat"] = value;
}
}
/// <summary>Gets or sets a value that indicates whether the message format can be automatically selected.</summary>
/// <returns>true if the message format can be automatically selected; otherwise, false. </returns>
[ConfigurationProperty("automaticFormatSelectionEnabled")]
public bool AutomaticFormatSelectionEnabled
{
get
{
return (bool)base["automaticFormatSelectionEnabled"];
}
set
{
base["automaticFormatSelectionEnabled"] = value;
}
}
/// <summary>Gets or sets the flag that specifies whether a FaultException is generated when an internal server error (HTTP status code: 500) occurs.</summary>
/// <returns>Returns true if the flag is enabled; otherwise returns false.</returns>
[ConfigurationProperty("faultExceptionEnabled")]
public bool FaultExceptionEnabled
{
get
{
return (bool)base["faultExceptionEnabled"];
}
set
{
base["faultExceptionEnabled"] = value;
}
}
protected override ConfigurationPropertyCollection Properties
{
get
{
if (this.properties == null)
{
this.properties = new ConfigurationPropertyCollection
{
new ConfigurationProperty("helpEnabled", typeof(bool), false, null, null, ConfigurationPropertyOptions.None),
new ConfigurationProperty("defaultBodyStyle", typeof(WebMessageBodyStyle), WebMessageBodyStyle.Bare, null, null, ConfigurationPropertyOptions.None),
new ConfigurationProperty("defaultOutgoingResponseFormat", typeof(WebMessageFormat), WebMessageFormat.Xml, null, null, ConfigurationPropertyOptions.None),
new ConfigurationProperty("automaticFormatSelectionEnabled", typeof(bool), false, null, null, ConfigurationPropertyOptions.None),
new ConfigurationProperty("faultExceptionEnabled", typeof(bool), false, null, null, ConfigurationPropertyOptions.None)
};
}
return this.properties;
}
}
/// <summary>Gets the type of the behavior enabled by this configuration element.</summary>
/// <returns>The <see cref="T:System.Type" /> for the behavior enabled with the configuration element: <see cref="T:System.ServiceModel.Description.WebHttpBehavior" />.</returns>
public override Type BehaviorType
{
get
{
return typeof(ExtendedWebHttpBehavior);
}
}
protected override object CreateBehavior()
{
return new ExtendedWebHttpBehavior
{
HelpEnabled = this.HelpEnabled,
DefaultBodyStyle = this.DefaultBodyStyle,
DefaultOutgoingResponseFormat = this.DefaultOutgoingResponseFormat,
AutomaticFormatSelectionEnabled = this.AutomaticFormatSelectionEnabled,
FaultExceptionEnabled = this.FaultExceptionEnabled
};
}
}
That's the web.config
<system.serviceModel>
<diagnostics>
<messageLogging logMalformedMessages="true" logMessagesAtTransportLevel="true" />
</diagnostics>
<bindings>
<webHttpBinding>
<binding name="regularService" />
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="AjaxBehavior">
<extendedWebHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="extendedWebHttp" type="MyNamespace.ExtendedWebHttpElement, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
<service name="MyWebService">
<endpoint address="" behaviorConfiguration="AjaxBehavior"
binding="webHttpBinding" bindingConfiguration="regularService"
contract="IMyWebService" />
</service>
</services>
Note: The behavior extension should be in one line EXACTLY as is (there's a bug in WCF).
That's my client side (part of our custom proxy)
public void Invoke<T>(string action, object prms, JsAction<T> successCallback, JsAction<WebServiceException> errorCallback = null, JsBoolean webGet = null)
{
Execute(new WebServiceRequest { Action = action, Parameters = prms, UseGetMethod = webGet },
t =>
{
successCallback(t.As<T>());
},
(req, message, err)=>
{
if (req.status == 400) //Bad request - that's what we've specified in the WCF error handler.
{
var details = JSON.parse(req.responseText).As<JsonErrorDetails>();
var ex = new WebServiceException()
{
Message = details.Message,
StackTrace = details.StackTrace,
Type = details.ExceptionType
};
errorCallback(ex);
}
});
}

WCF Constructor with parameter / Custom Behavior Created In Code

I've a must to create wcf service with parameter.
I'm following this http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/8f18aed8-8e34-48ea-b8be-6c29ac3b4f41
First this is that I don't know how can I set this custom behavior "MyServiceBehavior" in my Web.config in ASP.NET MVC app that will host it.
As far as I know behaviors must be declared in section in wcf.config.
How can I add reference there to my behavior class from service assembly?
An second thing is that in the following example they have created local host (they use
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
to host in console application), but how I can add headers
OperationContext.Current.OutgoingMessageHeaders.Add ...
used to initialize constructor when I use in my WPF client application service reference and it will already create instance of web service "client" class
PBSDataCacheSyncContractClient client = new PBSDataCacheSyncContractClient();
is't it too late? Or when I have my own custom behavior can I do something like this:
PBSDataCacheSyncContractClient client = new PBSDataCacheSyncContractClient(my var for service constructor) ?
Regards,
Daniel Skowroński
EDIT: 31-05-2010
#manunt
I've improved my second question.
For answer to my first question, I've managed to create custom extenstion but I can't register it.
My scenario:
I have definitions for my web service in WCF library (interface, contract, implementation of IInstanceProvider, BehaviorExtensionElement)
then I reference it to another project ASP.NET application
inside ASP.NET application I have WCF service file and it is pointed to my class from WCF library
all my configuration is declared in web.config
In my WCF library I have:
namespace PBS.SyncService
{
using System;
using System.Data;
using System.Collections.ObjectModel;
using System.ServiceModel;
using Microsoft.Synchronization.Data;
using System.ServiceModel.Activation;
using Microsoft.Synchronization.Data.Server;
using System.Data.SqlClient;
using System.Collections.Generic;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
[XmlSerializerFormat()]
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public partial class PBSDataCacheSyncService : object, IPBSDataCacheSyncContract
{
private PBSDataCacheServerSyncProvider _serverSyncProvider;
public PBSDataCacheSyncService()
{
this._serverSyncProvider = new PBSDataCacheServerSyncProvider();
}
public PBSDataCacheSyncService(long doctorId)
{
this._serverSyncProvider = new PBSDataCacheServerSyncProvider();
this._serverSyncProvider.DoctorId = doctorId;
this._serverSyncProvider.InitializeCustomSyncProvider();
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public virtual SyncContext ApplyChanges(Microsoft.Synchronization.Data.SyncGroupMetadata groupMetadata, DataSet dataSet, Microsoft.Synchronization.Data.SyncSession syncSession)
{
return this._serverSyncProvider.ApplyChanges(groupMetadata, dataSet, syncSession);
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public virtual SyncContext GetChanges(Microsoft.Synchronization.Data.SyncGroupMetadata groupMetadata, Microsoft.Synchronization.Data.SyncSession syncSession)
{
return this._serverSyncProvider.GetChanges(groupMetadata, syncSession);
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public virtual SyncSchema GetSchema(Collection<string> tableNames, Microsoft.Synchronization.Data.SyncSession syncSession)
{
return this._serverSyncProvider.GetSchema(tableNames, syncSession);
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public virtual SyncServerInfo GetServerInfo(Microsoft.Synchronization.Data.SyncSession syncSession)
{
return this._serverSyncProvider.GetServerInfo(syncSession);
}
public bool InitializeCustomSyncProvider(long doctorId)
{
this._serverSyncProvider.DoctorId = doctorId;
return this._serverSyncProvider.InitializeCustomSyncProvider();
}
}
[XmlSerializerFormat()]
[ServiceContractAttribute()]
public interface IPBSDataCacheSyncContract
{
[OperationContract()]
SyncContext ApplyChanges(Microsoft.Synchronization.Data.SyncGroupMetadata groupMetadata, DataSet dataSet, Microsoft.Synchronization.Data.SyncSession syncSession);
[OperationContract()]
SyncContext GetChanges(Microsoft.Synchronization.Data.SyncGroupMetadata groupMetadata, Microsoft.Synchronization.Data.SyncSession syncSession);
[OperationContract()]
SyncSchema GetSchema(Collection<string> tableNames, Microsoft.Synchronization.Data.SyncSession syncSession);
[OperationContract()]
SyncServerInfo GetServerInfo(Microsoft.Synchronization.Data.SyncSession syncSession);
[OperationContract()]
bool InitializeCustomSyncProvider(long doctorId);
[OperationContract()]
string[] GetSyncAdapterInfo();
}
public class PBSDataCacheSyncProvider : IInstanceProvider
{
public object GetInstance(InstanceContext instanceContext, Message message)
{
string doctorId = message.Headers.GetHeader<string>("DoctorId", "http://***/SyncService.svc");
if (doctorId != null)
{
return new PBSDataCacheSyncService(Convert.ToInt64(doctorId));
}
else
{
return new PBSDataCacheSyncService();
}
}
public object GetInstance(InstanceContext instanceContext)
{
return new PBSDataCacheSyncService();
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
}
}
public class PBSDataCacheSyncBehavior : BehaviorExtensionElement, IServiceBehavior
{
PBSDataCacheSyncProvider pbsProvider = new PBSDataCacheSyncProvider();
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { }
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
ed.DispatchRuntime.InstanceProvider = this.pbsProvider;
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }
public override Type BehaviorType
{
get { return typeof(PBSDataCacheSyncBehavior); }
}
protected override object CreateBehavior()
{
return new PBSDataCacheSyncBehavior();
}
}
}
My WCF Service file has name: SyncService.svc and in my makrup I have:
<%# ServiceHost Language="C#" Debug="true" Service="PBS.SyncService.PBSDataCacheSyncService" CodeBehind="PBS.SyncService.PBSDataCache.Server.SyncContract.cs" %>
My web.config:
<service name="PBS.Web.SyncService" behaviorConfiguration="behPBSDataCacheSyncBehavior">
<host>
<baseAddresses>
<add baseAddress="http://***/SyncService.svc" />
</baseAddresses>
</host>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<endpoint address="" binding="basicHttpBinding" contract="PBS.SyncService.IPBSDataCacheSyncContract" />
</service>
<serviceBehaviors>
<behavior name="behPBSDataCacheSyncBehavior">
<PBSDataCacheSyncBehavior /> <!-- this element is being ignored -->
</behavior>
</serviceBehaviors>
<extensions>
<behaviorExtensions>
<add name="PBSDataCacheSyncBehavior" type="PBS.SyncService.PBSDataCacheSyncBehavior, PBS.SyncService,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
Can you tell me what I'm missing in this point?
Why parser ignores my custom extension declaration?
I have following error:
Configuration Error
Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.
Parser Error Message: An error occurred creating the configuration section handler for system.serviceModel/behaviors: Extension element 'PBSDataCacheSyncBehavior' cannot be added to this element. Verify that the extension is registered in the extension collection at system.serviceModel/extensions/behaviorExtensions.
Parameter name: element
EDIT: 01-06-2010
Problem with parser resolved by typing all the declaration in one single line.
I still don't know how to add header when I have service reference.
In my WPF application I have only client instance witch implements my IPBSDataCacheSyncContract autogenerated by Service Reference.
And when I initialize it it only has constructors:
public PBSDataCacheSyncContractClient() {
}
public PBSDataCacheSyncContractClient(string endpointConfigurationName) :
base(endpointConfigurationName) {
}
public PBSDataCacheSyncContractClient(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
public PBSDataCacheSyncContractClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
public PBSDataCacheSyncContractClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress) {
}
Where I can add headers?
"As for the second question - you should define message contract with needed headers in it and provide header values for each message separately." Could you be more specific?
EDIT: 02-06-2010
I've encountered other issue.
When I have now my configuration httpGetEnabled is ignored... :
<serviceBehaviors>
<behavior name="behPBSDataCacheSyncBehavior">
<PBSDataCacheSyncBehavior />
<serviceMetadata httpGetEnabled="true" /><!-- ignored -->
<serviceDebug includeExceptionDetailInFaults="true" /><!-- ignored -->
</behavior>
</serviceBehaviors>
How can I fix it?
EDIT: 02-06-2010
OK I've figured workaround. Still it is weird but it works!
My problem was with web.config. And none name behavior entry entry is recognized by my service and not any other... So I simply added no name behavior to collection.
<serviceBehaviors>
<behavior name="">
<PBSDataCacheSyncBehavior />
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
<behavior name="behPBSDataCacheSyncBehavior">
<PBSDataCacheSyncBehavior />
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
And I add header in my code this way:
int doctorId = 2;
Sync.PBSDataCacheSyncContractClient client = new Sync.PBSDataCacheSyncContractClient();
new OperationContextScope (client.InnerChannel);
OperationContext.Current.OutgoingMessageHeaders.Add(
MessageHeader.CreateHeader("DoctorId", "http://***/SyncService.svc", doctorId));
I've changed topic to be more useful.
HTH
Regards,
Daniel Skowroński
I know what the problem is with the behavior not being found and why you need the hack with the behavior with no name.
If you look at this line in your svc markup file:
<%# ServiceHost Language="C#" Debug="true" Service="PBS.SyncService.PBSDataCacheSyncService" CodeBehind="PBS.SyncService.PBSDataCache.Server.SyncContract.cs" %>
and this line in your web.Config:
<service name="PBS.Web.SyncService" behaviorConfiguration="behPBSDataCacheSyncBehavior">
You will notice that the name specified in the service tag is different from the Service class specified in the Service attribute in the markup file.
I think it should be something like this:
instead of
<service name="PBS.Web.SyncService" behaviorConfiguration="behPBSDataCacheSyncBehavior">
this
<service name="PBS.SyncService.PBSDataCacheSyncService" behaviorConfiguration="behPBSDataCacheSyncBehavior">
These two values I think have to the same not sure but in my case the two values were different and I had to do the blank service name hack. But by setting both values the same, it worked. It found the behavior without needing the blank one and I was able to access my wsdl.
Answer for the first question you can find here.
Regarding error you are getting - do not split definition of your extension into two lines, because xml parser cannot handle that.
A sample how to define custom headers without specifying message contract:
var client = new Service1Client();
new OperationContextScope(client.InnerChannel);
MessageHeader<string> typedHeader = new MessageHeader<string>("headercontent");
MessageHeader header = typedHeader.GetUntypedHeader("myheader", "myns");
OperationContext.Current.OutgoingMessageHeaders.Add(header);