WCF TCP configuration/custom principal issue?

WCF TCP configuration/custom principal issue?

Old forum URL: forums.lhotka.net/forums/t/10011.aspx


itogroup posted on Tuesday, February 01, 2011

I'm using CSLA .NET 3.8.2.0 and working on moving from using the Remoting-based DataPortal to the WCF-based DataPortal, specifically using TCP.

I can get everything working beautifully using a basicHttpBinding, but am having mixed luck with the netTcpBinding.

I have a business object which doesn't require any security roles that I've been using to test the DataPortal. Using TCP I can get the object with the generic principal on the thread and I can get it after calling the Logout() method on my custom principal (to get the custom principal on the thread). This means that we have IIS and WAS working correctly and there aren't any firewall issues.

When I call the Login method on my custom principal, I get an exception in WcfProxy.Fetch(Type objectType, object criteria, DataPortalContext context) when it calls response = svr.Fetch(new FetchRequest(objectType, criteria, context)); The exception's message is "There was an error while trying to deserialize parameter http://ws.lhotka.net/WcfDataPortal:request.  Please see InnerException for more details." but the InnerException is null.

I've added a LoadPrincipal(string username) method to my principal, created a custom UserNamePasswordValidator, created a custom IAuthorizationPolicy, and subclassed WcfProxy to override its GetChannelFactory() as described in Using CSLA .NET 3.0.

As I said above, when I use the basicHttpBinding, the login works fine and my program runs perfectly. It's just when I switch over to the netTcpBinding that the login fails.

I've re-read chapter 1 of Using CSLA.NET Version 3.0. I'm hoping someone has some insight before I have to delve into Bustamante's Learning WCF again.

Below are the relevant parts of the client's app.config:

<configuration>
	<appSettings>
		<add key="CslaAuthentication" value="Csla"/>
		<add key="CslaDataPortalProxy" value="Harld.Data.HarldWcfProxy, Harld.Data"/>
	</appSettings>
	<system.serviceModel>
		<client>
			<endpoint	name="HarldWcfDataPortal"
						address="net.tcp://servername.domain.tld:42753/WcfPortal.svc"
						binding="netTcpBinding"
						contract="Csla.Server.Hosts.IWcfPortal" bindingConfiguration="WcfPortalBinding">
			</endpoint>
			<!--<endpoint	name="HarldWcfDataPortal"
						address="https://servername.domain.tld:4433/WcfPortal.svc"
						binding="basicHttpBinding"
						contract="Csla.Server.Hosts.IWcfPortal" 
						bindingConfiguration="BasicSslBinding">
			</endpoint>-->
		</client>
		<bindings>
			<basicHttpBinding>
				<binding
					name="BasicSslBinding"
					maxReceivedMessageSize="2147483647"
					maxBufferPoolSize="2147483647"
					receiveTimeout="00:01:00"
					sendTimeout="00:01:00">
					<security mode="Transport">
						<message clientCredentialType="UserName" algorithmSuite="Default" />
					</security>
					<readerQuotas
						maxDepth="2147483647"
						maxStringContentLength="2147483647"
						maxArrayLength="2147483647"
						maxBytesPerRead="2147483647"
						maxNameTableCharCount="2147483647" />
				</binding>
			</basicHttpBinding>
			<netTcpBinding>
				<binding
					name="WcfPortalBinding"
					maxReceivedMessageSize="2147483647"
					maxBufferPoolSize="2147483647"
					receiveTimeout="00:01:00"
					sendTimeout="00:01:00">
					<security mode="Transport">
						<message clientCredentialType="UserName" algorithmSuite="Default" />
					</security>
					<readerQuotas
						maxDepth="2147483647"
						maxStringContentLength="2147483647"
						maxArrayLength="2147483647"
						maxBytesPerRead="2147483647"
						maxNameTableCharCount="2147483647" />
				</binding>
			</netTcpBinding>
		</bindings>
	</system.serviceModel>
</configuration>

And the relevant parts of the server's web.config:

<configuration>
	<system.serviceModel>
		<serviceHostingEnvironment>
			<baseAddressPrefixFilters>
				<add prefix="https://servername.domain.tld:4433"/>
				<add prefix="net.tcp://servername.domain.tld:42753"/>
			</baseAddressPrefixFilters>
		</serviceHostingEnvironment>
		<services>
			<service name="Csla.Server.Hosts.WcfPortal" behaviorConfiguration="DebugBehavior">
				<endpoint contract="Csla.Server.Hosts.IWcfPortal"
						  address="https://servername.domain.tld:4433/WcfPortal.svc"
						  binding="basicHttpBinding"
						  bindingConfiguration="BasicSslBinding" />
				<endpoint contract="Csla.Server.Hosts.IWcfPortal"
						  address="net.tcp://servername.domain.tld:42753/WcfPortal.svc"
						  binding="netTcpBinding" 
						  bindingConfiguration="WcfPortalBinding">
				</endpoint>
			</service>
		</services>
		<bindings>
			<basicHttpBinding>
				<binding
					name="BasicSslBinding"
					maxReceivedMessageSize="2147483647"
					maxBufferPoolSize="2147483647"
					receiveTimeout="00:01:00"
					sendTimeout="00:01:00">
					<security mode="Transport">
						<message clientCredentialType="UserName" algorithmSuite="Default" />
					</security>
					<readerQuotas
						maxDepth="2147483647"
						maxStringContentLength="2147483647"
						maxArrayLength="2147483647"
						maxBytesPerRead="2147483647"
						maxNameTableCharCount="2147483647" />
				</binding>
			</basicHttpBinding>
			<netTcpBinding>
				<binding
					name="WcfPortalBinding"
					maxReceivedMessageSize="2147483647"
					maxBufferPoolSize="2147483647"
					receiveTimeout="00:01:00"
					sendTimeout="00:01:00">
					<security mode="Transport">
						<message clientCredentialType="UserName" algorithmSuite="Default" />
					</security>
					<readerQuotas
						maxDepth="2147483647"
						maxStringContentLength="2147483647"
						maxArrayLength="2147483647"
						maxBytesPerRead="2147483647"
						maxNameTableCharCount="2147483647" />
				</binding>
			</netTcpBinding>
		</bindings>
		<behaviors>
			<serviceBehaviors>
				<behavior name="DebugBehavior">
					<serviceDebug includeExceptionDetailInFaults="true" />
				</behavior>
				<behavior name="HarldAuthBehavior">
					<serviceDebug includeExceptionDetailInFaults="true" />
					<serviceAuthorization principalPermissionMode="Custom">
						<authorizationPolicies>
							<add policyType="Harld.Data.PrincipalPolicy, Harld.Data" />
						</authorizationPolicies>
					</serviceAuthorization>
					<serviceCredentials>
						<userNameAuthentication
							userNamePasswordValidationMode="Custom"
							customUserNamePasswordValidatorType="Harld.Data.CredentialValidator, Harld.Data" />
					</serviceCredentials>
					<serviceMetadata httpGetEnabled="true" />
				</behavior>
				<behavior name="MexBehavior">
					<serviceMetadata
					  httpsGetEnabled="true" httpsGetUrl="https://servername.domain.tld:4433/Furniture/mex" />
				</behavior>
			</serviceBehaviors>
		</behaviors>
	</system.serviceModel>
</configuration>

RockfordLhotka replied on Tuesday, February 01, 2011

I don't know the answer for sure. Here are some things to think about.

By default WCF uses the DataContractSerializer, which isn't sufficient for CSLA. So the WCF server-side portal includes functionality to force the use of the NetDataContractSerializer.

It is possible that the TCP binding doesn't honor the same extension attributes as the HTTP bindings, and so the NDCS isn't being used. If the DCS were used, serialization and/or deserialization would fail because CSLA forms messages that the DCS simply can't handle.

I don't know of an easy way to determine which serializer is being executed. You can probably use fiddler to look at the data stream to find out though. The XML created by the DCS and NDCS are extremely different. The DCS XML is much cleaner, because it lacks nearly all metadata (like assembly names), while the NDCS XML is more comprehensive and includes everything necessary to clone .NET objects, including full .NET type information.

itogroup replied on Tuesday, February 08, 2011

I'm having some issues getting Fiddler configured correctly (I've never used it before). I have Fiddler running on the client machine. The NetTcpBinding doesn't have the ProxyAddress property (as BasicHTTPBinding and WSHttpBinding do). I added a default proxy to the system.net section of the client app.config

<defaultProxy useDefaultCredentials="true">
       <proxy bypassonlocal="False" proxyaddress="http://127.0.0.1:8888" />
</defaultProxy>

Fiddler says it's capturing on all processes, but nothing appears when I try to fetch an object through the DataPortal over the TCP binding. Any thoughts? I also tried explicitly adding <add key="CslaSerializationFormatter" value="NetDataContractSerializer" /> to both the web.config and app.config, no change.

 

Though, I did find some more detail about the exception being thrown:

"DataTable does not support schema inference from Xml."

   at System.Data.DataTable.ReadXml(XmlReader reader, XmlReadMode mode, Boolean denyResolving)
   at System.Data.DataTable.ReadXmlSerializable(XmlReader reader)
   at System.Data.DataTable.System.Xml.Serialization.IXmlSerializable.ReadXml(XmlReader reader)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.ReadIXmlSerializable(XmlSerializableReader xmlSerializableReader, XmlReaderDelegator xmlReader, XmlDataContract xmlDataContract, Boolean isMemberType)
   at System.Runtime.Serialization.XmlDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserializeInSharedTypeMode(XmlReaderDelegator xmlReader, Int32 declaredTypeID, Type declaredType, String name, String ns)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(XmlReaderDelegator xmlReader, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle, String name, String ns)
   at ReadHarldIdentityFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
   at System.Runtime.Serialization.ClassDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserializeInSharedTypeMode(XmlReaderDelegator xmlReader, Int32 declaredTypeID, Type declaredType, String name, String ns)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(XmlReaderDelegator xmlReader, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle, String name, String ns)
   at ReadHarldPrincipalFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
   at System.Runtime.Serialization.ClassDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserializeInSharedTypeMode(XmlReaderDelegator xmlReader, Int32 declaredTypeID, Type declaredType, String name, String ns)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(XmlReaderDelegator xmlReader, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle, String name, String ns)
   at ReadDataPortalContextFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
   at System.Runtime.Serialization.ClassDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserializeInSharedTypeMode(XmlReaderDelegator xmlReader, Int32 declaredTypeID, Type declaredType, String name, String ns)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(XmlReaderDelegator xmlReader, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle, String name, String ns)
   at ReadFetchRequestFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
   at System.Runtime.Serialization.ClassDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserializeInSharedTypeMode(XmlReaderDelegator xmlReader, Int32 declaredTypeID, Type declaredType, String name, String ns)
   at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(XmlReaderDelegator xmlReader, Type declaredType, String name, String ns)
   at System.Runtime.Serialization.NetDataContractSerializer.InternalReadObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName)
   at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName)
   at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameterPart(XmlDictionaryReader reader, PartInfo part, Boolean isRequest)

tmg4340 replied on Tuesday, February 08, 2011

I can't help you with the Fiddler issues, but I do have experience with that exception.  This is what you get when your DataTable is empty - i.e. it has no rows and no columns.  .NET's XML serializers have two requirements when trying to serialize a DataTable:

1. It must have at least one column defined.

2. It must have a name.

I don't really understand #2, but I can tell you it's a requirement...

So, ultimately your problem is that whatever is constructing your DataTable is not building a schema for it, and when that process returns no rows, the resulting DataTable object is empty.

HTH

- Scott

itogroup replied on Wednesday, February 23, 2011

Scott,

I finally got a chance to verify that the table has a name and the schema is getting loaded correctly, even if there are no rows. Plus it serializes and deserializes fine through the HTTP-based channels.

Rocky's suggestion about the mismatched serializers seems like it could be right, I just can't seem to get at the underlying byte stream to confirm.

Rob

Copyright (c) Marimer LLC