WCF .NET 4 OutputCaching with Stream doesn't seem to work - wcf

I'm having problems with OutputCaching over a WCF REST service on .NET4-IIS7. My service has a custom Authorization scheme (by implementing ServiceAuthorizationManager), one which should take place on every request, and any caching must be done after the request is authorized. So far, this seems to work, only the caching part I can't see happening.
I have the following OperationContract:
[OperationContract]
[AspNetCacheProfile("PageZIP")]
[WebGet(UriTemplate = "pages/{page_id}")]
System.IO.Stream getPage(string page_id);
And the following web.config:
<system.web>
<compilation targetFramework="4.0" />
<customErrors mode="Off" />
<authentication mode="None" />
<caching>
<outputCache enableOutputCache="true"/>
<outputCacheSettings>
<outputCacheProfiles>
<add name="PageZIP" duration="7200" location="ServerAndClient"
varyByParam="page_id" />
</outputCacheProfiles>
</outputCacheSettings>
</caching>
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceAuthorization serviceAuthorizationManagerType="api.Authorization, api" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint transferMode="StreamedResponse" automaticFormatSelectionEnabled="false" defaultOutgoingResponseFormat="Json" />
</webHttpEndpoint>
</standardEndpoints></system.serviceModel>
I can see the new response headers filled with client-side cache information when I call the service, but caching at the server doesn't seem to work. My custom log shows that my api.Authorization class is (rightly) being called, but my getPage() method is also executing as normal, up to the point where my file is being .OpenRead() into a Stream and returned:
public System.IO.Stream getPage(string page_id) {
File zip = FileMapper.getZip(pid);
ctx.OutgoingResponse.Headers.Clear();
ctx.OutgoingResponse.Headers.Add("Content-Disposition", "attachment; filename=" + page_id + ".zip");
ctx.OutgoingResponse.Headers.Add("Content-Length", zip.size.ToString());
ctx.OutgoingResponse.ContentType = "application/zip";
Log.write("OpenRead", LogType.Info);
return System.IO.File.OpenRead(zip.path);
}
If output is being cached, this method shouldn't be executed at all... I expect the Stream to be cached and be served directly, without queries to the database and disk reads. Every zipfile is about 1 MB in size.
What am I missing or doing wrong?

After playing with settings for a while, the answer came out: OutputCache won't work if transferMode is streaming, even if you implement your own OutputCacheProvider. The reason behind this is that, in a streamed response scenario, you're telling WCF not to buffer your response in memory, and try to read it from wherever it is and send it down to transport level. OutputCache depends on the object being fully in-memory before it's returned, so that WCF can keep the reference to it and put that on cache.
It's up to you to see in your scenario if enabling streaming without output cache is faster than reading and keeping the object in-memory so you can output it directly.

Related

Handling REST SWT tokens in WCF WIF pipeline is not working

I'm struggling on a setup mentioned in the subject line and am wondering if someone can help me.
Essentially, what I have is a WCF service and I want to achieve that the user can authenticate against the ACS using a custom login page (using the javascript with required information from ACS).
After doing that the user should get redirected to the WCF service using the provided SWT token. I am using the SimpleWebTokenHandler as a basis for the SWT token handling, but I'm not sure it's playing any role in this.
Here's the Web.config I'm running
<configuration>
<configSections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral" />
</configSections>
...
<system.serviceModel>
<diagnostics>
</diagnostics>
<services>
<service name="WcfWifSwtAcs.Service1">
<endpoint address="xmlService" binding="webHttpBinding" bindingConfiguration="" behaviorConfiguration="restPoxBehaviour" name="xmlServiceEndpoint" contract="WcfWifSwtAcs.IService1" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="restPoxBehaviour">
<webHttp helpEnabled="true" />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials useIdentityConfiguration="true">
...
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add scheme="http" binding="ws2007FederationHttpBinding" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<bindings>
<ws2007FederationHttpBinding>
<binding name="">
<security mode="Message">
<message
issuedTokenType="http://schemas.xmlsoap.org/ws/2009/11/swt-token-profile-1.0">
<issuerMetadata address="https://xxxx.accesscontrol.windows.net/v2/wstrust/13/certificate/mex" />
</message>
</security>
</binding>
</ws2007FederationHttpBinding>
</bindings>
</system.serviceModel>
<system.webServer>
...
</system.webServer>
<system.identityModel>
<identityConfiguration>
<audienceUris>
<add value="http://localhost:56782/Service1.svc" />
</audienceUris>
<issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<trustedIssuers>
<add thumbprint="XXX" name="xxx.accesscontrol.windows.net" />
</trustedIssuers>
</issuerNameRegistry>
<issuerTokenResolver type="SimpleWebToken.CustomIssuerTokenResolver, WcfWifSwtAcs" />
<securityTokenHandlers>
<clear/>
<add type="SimpleWebToken.SimpleWebTokenHandler, WcfWifSwtAcs"/>
</securityTokenHandlers>
</identityConfiguration>
</system.identityModel>
</configuration>
Now I can see, that the authentication happens and that the browser is redirected with the body to the service. I can also see that the SimpleWebToken handler get's instantiated and the token type URI is being requested. But that's almost all that happens. No actual token handling verification and whatsoever is happnening.
This is the token that get's sent to the service (after parsing).
wa=wsignin1.0&
wresult=
<t:RequestSecurityTokenResponse
xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<t:Lifetime>
<wsu:Created
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2013-02-13T23:14:30.159Z</wsu:Created>
<wsu:Expires
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2013-02-13T23:24:30.159Z</wsu:Expires>
</t:Lifetime>
<wsp:AppliesTo
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<EndpointReference
xmlns="http://www.w3.org/2005/08/addressing">
<Address>http://localhost:56782/Service1.svc</Address>
</EndpointReference>
</wsp:AppliesTo>
<t:RequestedSecurityToken>
<wsse:BinarySecurityToken
wsu:Id="uuid:58e2fb15-dd1a-40bd-8ff0-ae24e22e6efe"
ValueType="http://schemas.xmlsoap.org/ws/2009/11/swt-token-profile-1.0"
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
BASE64 DATA==
</wsse:BinarySecurityToken>
</t:RequestedSecurityToken>
<t:TokenType>http://schemas.xmlsoap.org/ws/2009/11/swt-token-profile-1.0</t:TokenType>
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
</t:RequestSecurityTokenResponse>
Service itself is braindead simple, with following signature.
[OperationContract]
[WebInvoke(UriTemplate = "/GetData/{id}", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
string GetData(string id);
Any ideas? I've been verifying that the uri's, hostnames, thumbprints etc. are all valid. Also the service tracing doesn't really show up anything that is related either to token handling or exceptions in the token verification.
Somehow it almost seems that the token doesn't get even passed to the handler. At least all the claims and other authentication information is missing (null).
I would appreciate if someone points me to a direction of either where can I debug or if I'm missing something really obvious (which might also always be the case).
P.S. I know I could achieve it with custom authentication modules and whatsoever, I'd rather get it running with WIF (it's becoming fundamental as I've spend more time on this as I really wanted and I'm very stubborn :p).
Soo, dedication will bring one to a solution. Although I initially thought that this can't be done, it's apparent that it actually can. I'll put the solution here, as maybe there are other people who find it useful.
First of all, WCF REST services are using webHttpBinding, which according to MS documentation does not support the Windows Identity Foundation and claims handling in the pipeline. Actually it does. Not in the WCF pipeline, but as the IIS module in web authentication flow.
First, you need to add the following modules to Web.config file.
<system.webServer>
<modules runManagedModulesForAllRequests="true">
<add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" preCondition="managedHandler" />
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" preCondition="managedHandler" />
</modules>
</system.webServer>
There's a caveat tho. You need still the <configSections> from my original posting. The problem is that you need, in VisualStudio, to mark the System.IdentyModel* assemblies as CopyLocal items (in the properties window). Otherwise you'll get some cryptic exception that assembly cannot be loaded for the configuration section. NB! It only happens if you are loading these two modules and doesn't happen when those modules are not getting loaded. Didn't have any will to investigate that thing further, perhaps someone knows better what's the cause there.
Next if for any reason you plan to use the SWT token handling sample from MS WIF code, there are a couple of bugs that need to be fixed, otherwise the token parsing just won't happen or you will get invalid signatures out of the token verification.
SimpleWebToken.cs you need to fix the SwtBaseTime as it is initialized incorrectly and the security token creation fails afterwards:
From
public static DateTime SwtBaseTime = new DateTime( 1970, 1, 1, 0, 0, 0, 0 ); // per SWT psec
To
public static DateTime SwtBaseTime = new DateTime( 1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc ); // per SWT psec
SimpleWebTokenHandler.cs you need to fix the casing of the following values:
From
const string BinarySecurityToken = "binarySecurityToken";
const string ValueType = "valueType";
To
const string BinarySecurityToken = "BinarySecurityToken";
const string ValueType = "ValueType";
CustomIssuerTokenResolver.cs you need to fix the key that is created as it's initalized with a UTF8 bytes, but it should actually get initialized with decoded Base64 bytes:
From
key = new InMemorySymmetricSecurityKey(UTF8Encoding.UTF8.FromBase64String(base64Key));
To
key = new InMemorySymmetricSecurityKey(System.Convert.FromBase64String(base64Key));
After you've fixed all this, everything sits in place. The authenticators and authorizators are getting called and voilà, suddenly you have a WCF Service exposed as REST endpoint and all the claims etc. are also working.
I think your issue may be with the SWTTokenHandler in this sample: http://code.msdn.microsoft.com/vstudio/Custom-Token-ddce2f55
In CanReadToken(), it checks to see if the token is a BinarySecurityToken of type SWT:
if ( reader.IsStartElement( BinarySecurityToken )
&& ( reader.GetAttribute( ValueType ) == SimpleWebTokenConstants.ValueTypeUri ) )
But the constant BinarySecurityToken is defined as:
const string BinarySecurityToken = "binarySecurityToken";
Note the lower-case "b". XML elements are case sensitive, and the actual element is "BinarySecurityToken" with a capital B. This will cause the handler to return false in CanReadToken(), causing WIF to believe it doesn't have a handler registered for this token type.

WCF Authentication Service Proxy - CookieContainer not available

I have enabled the ASP.Net authentication service, as recommended by msdn. I am then attempting to use the service via a console app or winforms app (by adding a service reference to my local WCF service). I am doing custom authentication and transport security (so I am handling the AuthenticationService.Authenticating event in my Global.asax which works fine).
The authentication itself works fine, but the proxy created by adding the Service Reference does not include the CookieContainer property. This is obviously a problem when I try to pass the cookie token to subsequent services which require authentication.
Also, in the following client code, the IsLoggedIn() returns false, I'm guessing this is related to no cookie container being present.
ServiceReference1.AuthenticationServiceClient client =
new ServiceReference1.AuthenticationServiceClient();
bool isLoggedIn = client.Login("test", "test", "", true); //returns TRUE
bool check = client.IsLoggedIn(); //returns FALSE
Here is my web.config on the service:
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.web.extensions>
<scripting>
<webServices>
<authenticationService enabled="true"
requireSSL = "false"/>
</webServices>
</scripting>
</system.web.extensions>
<system.serviceModel>
<services>
<service name="System.Web.ApplicationServices.AuthenticationService"
behaviorConfiguration="AuthenticationServiceTypeBehaviors">
<endpoint contract="System.Web.ApplicationServices.AuthenticationService"
binding="basicHttpBinding"
bindingConfiguration="userHttps"
bindingNamespace="http://asp.net/ApplicationServices/v200"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="userHttps" allowCookies="true">
<!--<security mode="Transport" />-->
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="AuthenticationServiceTypeBehaviors">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
</system.serviceModel>
</configuration>
EDIT: Something else I should add, I did a Fiddler session of the service calling the Login method, and the cookie is being set and sent back to the client. But what am I supposed to do with no CookieContainer?
When this option is enabled the client will make sure all cookies received from a given web
service are stored and properly sent on each subsequent request in a transparent fashion.
But there is a catch: the cookie is only handled in the conversation with one web service.
What if you need to send the same cookies to different web services?
If you need to send the same cookies to multiple services, read this article: http://megakemp.wordpress.com/2009/02/06/managing-shared-cookies-in-wcf/
You need to configure the binding to allow cookies.
<system.ServiceModel>
<bindings>
<basicHttpBinding allowCookies="true">
</bindings>
Apparently, when adding a Service Reference (.Net 3.5+) to a WCF service on a client, the proxy class derives from System.ServiceModel.ClientBase. This class does not have a CookieContainer property (because the ClientBase supports non-HTTP protocols that have no concept of cookies).
http://netpl.blogspot.com/2011/11/managing-cookies-in-wcf-client.html
I could add a Web Reference instead, which would use the .Net 2.0 proxy class (and has CookieContainer property exposed) http://msdn.microsoft.com/en-us/library/bb628649.aspx. But I will most likely revisit my approach entirely and use custom headers and service behaviors to accomplish my goal.
Another option is to access the cookie container for the underlying channel like this:
var cookieManager = client.InnerChannel.GetProperty<IHttpCookieContainerManager>();
cookieManager.CookieContainer.Add(new Cookie(....));
For the above manager to be present, you need to set AllowCookies to true, e.g.:
<system.ServiceModel>
<bindings>
<basicHttpBinding allowCookies="true">
</bindings>

Returning Plain old XML with WCF

How can this be so difficult and why? Returning simple XML with no Name Spaces and an XML decaration. Creating the XML using XML Writer is easily done and if I output it to a file then great. How on earth can you return the contents via WCF. Using an XML Element is no good as you loose the XML declaration and using a string is no good as the output is wrapped up in a <string> element. Can't return an XML Document as it can't be serialised.
I know that there are many posts on this site, but none answer the question. I am using VB.NET (boy I wish I had the time to learn C#) and I cannot get the Data Contract to work either even using IXmlSerializer. An example of the output I need to send back via WCF service is:
<?xml version="1.0" encoding="utf-8"?>
<BookingResponse timestamp="" success="1">
<confirmation id="" track_code="" status="" notes="" tracking_url="" confirmed_at=""/>
</BookingResponse>
Unfortunately the service I am sending to will not accept anything other than this plain XML. The will not accept additional Name Spaces that WCF seems to put in all by it's self.
I am using the Online Rest Template for VB that is available for Visual Studio 2010.
Help! Please! someone must have an answer?
Here is some code:
<WebInvoke(UriTemplate:="BookJob", Method:="POST")>
Public Function Create(ByVal XMLBooking As Stream) As Stream
'DO SOME PROCESSING ON THE REQUEST - THIS ALL WORKS FINE.....
Dim str As String
'This CreateResponseXML function creates a String version of the XML, which is built using XMLWriter.
str = CreateResponseXML(True, vConfirmationID, sqlFuncs.BookingStatus("Confirmed"), "", "")
Dim memoryStream As New MemoryStream()
Using streamWriter As New StreamWriter(memoryStream)
streamWriter.Write(str)
streamWriter.Flush()
memoryStream.Position = 0
Return memoryStream
End Using
End Function
To add to the hassel they have to use a secure connection. I am using the Online Rest Template for VB that is available for Visual Studio 2010 which cuts down the entries in the web.config significantly compared to the normal one I see everywhere. It looks like this
:
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<customErrors mode="Off"/>
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
</system.webServer>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<bindings>
<webHttpBinding>
<binding transferMode="Streamed">
<security mode="Transport" />
</binding>
</webHttpBinding>
</bindings>
<standardEndpoints>
<webHttpEndpoint>
<!--
Configure the WCF REST service base address via the global.asax.vb file and the default endpoint
via the attributes on the <standardEndpoint> element below
-->
<standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true" faultExceptionEnabled="true">
<security mode="Transport" />
</standardEndpoint>
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
</configuration>
I tried returning a MemoryStream previous to this code, but the response they got was wrapped in a MemoryStream element and was gobbldegook (but this may have been due to the fact that I ommited the transferMode="Streamed" entry in the web.config. This version returns nothing at all.
Any Ideas?
Use WCFRestContrib it has POX formatter (Plain Old Xml) which is clean and without any namespaces.
The POX formatter uses the
System.Runtime.Serialization.DataContractSerializer, but unlike the
WCF REST Contrib xml formatter ([Discussed under Xml Formatter
Overview), does not serialize data contract namespaces or xml schema
attributes, it does not require namespaces to be specified in xml to
be deserialized and it does not require elements to be in a specific
order. This enables you to serve and accept very simple xml.
See POX Formatter Overview
Use Stream as the return value.

Very delayed IIS response from WCF service when returning fairly large data set

I have an IIS 7 hosted WCF service that is mainly being used to return data through the entity framework. One of the operation contracts/service methods returns a list of roughly 17000 very simple and fairly small entity objects. The response size ends up being roughly 6.5MB so it's not huge. When I host the service on my development machine with IIS Express, the service call is made and the data is posted back promptly (within 10 seconds). When I push the service to our server, the response takes an average of 1:46 seconds to come back. After doing some tracing on the web server, I discovered that the method that fetches the data only takes 6 seconds to return. That being the case, the server is taking roughly 1:40 seconds to prepare and then send the response (which I've confirmed in the web logs so it's not a network latency issue).
These are the only configurations that I have in the service definition:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
This is what my web.config looks like:
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0"/>
<httpRuntime executionTimeout="600" />
</system.web>
<system.serviceModel>
<services>
<service behaviorConfiguration="RGDataBehavior" name="WCFData.Web.Services.DataAccess">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="LargeBuffer" name="RGData_http" contract="WCFData.Web.Services.IDataAccess" listenUriMode="Explicit"/>
<host>
<baseAddresses>
<add baseAddress="http://myurl.com/Services/Data.svc"/>
</baseAddresses>
</host>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="LargeBuffer"
closeTimeout="00:10:00"
openTimeout="00:10:00"
receiveTimeout="00:10:00"
sendTimeout="00:10:00"
transferMode="StreamedResponse"
maxBufferPoolSize="2147483647"
maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647">
<readerQuotas maxDepth="2147483647"
maxArrayLength="2147483647"
maxBytesPerRead="2147483647"
maxStringContentLength="2147483647"
maxNameTableCharCount="2147483647"/>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="RGDataBehavior">
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
<serviceMetadata httpGetEnabled="false"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceAuthorization
serviceAuthorizationManagerType="WCFData.Web.Authentication.WCFAuthenticator,
Data.Web.Authentication"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
I've been busting my head over this and most of what I've found doesn't quite seem to fit the bill so I would love some help if anyone has suggestions. Thanks in advance.
I would recommend enabling WCF trace logging which might help get more timing numbers out of the WCF internals.
You could also, run the server through a profiling tool, and try to narrow down exactly what method is taking so long, but I'm not sure if there is a good way to do that through IIS.
If all else fails, you can always take some thread dumps while the server is processing (during this 1m40s processing time) and look at the stack trace for each thread to see what might be stuck. I usually do that using ntsd.exe command line debugger that installs with the Debugging Tools for Windows package.
Edit:
Here is another idea. Since you think it is related directly to the serialization, you can isolate the DataContractSerializer and time how long it takes to serialize your data. Code would look like:
[OperationContract]
public YourData[] YourServiceMethod()
{
YourData[] data = ... // get all your data here as usual.
// lets serialize it and time how long it takes
var timer = new System.Diagnostics.Stopwatch();
timer.Start();
var dataContractSerializer = new System.Runtime.Serialization.DataContractSerializer(data.GetType());
using (var memoryStream = new System.IO.MemoryStream())
{
dataContractSerializer.WriteObject(memoryStream, data);
}
timer.Stop();
System.Console.WriteLine(timer.ElapsedMilliseconds); // Log this, or whatever...
// return now as normal (WCF will re-serialize, talking even longer, but this is just a test anyway)
return data;
}
The answer to my question is more of a work around than a true solution to the problem. Changing the service method to return a byte array rather than a collection so that IIS wouldn't have to write an XML serialized collection into the response took care of the issue but it still doesn't explain why the delay was happening in the first place. I used the protobuf-net library to serialize my return collection to a byte array and then again on the client side to deserialize the array.

Authenticating a WCF call from Silverlight using ADAM (AD LDS)

I have a Silverlight 4 client that invokes several WCF services. We want the communication to be encrypted using SSL (I have this part solved already) and that every call be authenticated against AD LDS (ADAM), do you have any simple example showing how to make this work? There's plenty of documentation on the oh-so-many WCF options but I haven't been able to find a simple working example of this particular (but I think very common) scenario (SSL encryption + ADAM authentication + Silverlight). Any help or pointers greatly appreciated.
You can use CustomUserNameValidator in WCF:
http://msdn.microsoft.com/en-us/library/aa702565.aspx
http://nayyeri.net/custom-username-and-password-authentication-in-wcf-3-5
and in Custom Validator's Validate Method you can query ADAM to authenticate user.
Regards.
Try this link on the Codeplex website, it seems like the setup and configuration for the scenario you've described. It provides a thorough checklist of all of the required settings:
Intranet – Web to Remote WCF Using Transport Security (Trusted Subsystem, HTTP)
If this isn't you exact scenario, take a look at the following section that may fill the gaps:
Application Scenarios (WCF Security)
The answer may depend on how you will handle permissioning, since you use ASP.net's membership provider for these functions.
If you want claims based authorization ADFS 1.0 (not 2.0) supports ADAM. If you want a STS that has more options try codplex's StarterSTS
If you want to use Role-Based Administration, try Enterprise Library from Microsoft P&P, the ASP.net membership provider, or direct COM access to Authorization manager (formerly known as AzMan)
I prefer & use the claims-based approach:
Use ActiveDirectory + ADFS 2.0
(why not use the built in fault tolerance and replication of AD?, ADAM is a pain)
Silverlight Implementation of Ws-Trust as documented here:
http://www.leastprivilege.com/UsingSilverlightToAccessWIFSecuredWCFServices.aspx
Edgar, I'm also interested in any results you had, I am at the same place you were in.
Shoaib, I have looked at this but I think it is less desirable than using just the .config via ActiveDirectoryMembershipProvider, as then you are just using off the shelf components, not writing your own security system.
EDIT:
I hope this helps someone. I can't belive there is not a good example of this on the internet. It is simple enough. As I said before this is superior to using a custom authentication system.
Using AD LDS (ADAM) authentication with Silverlight compatible WCF calls (non wsHttp)
Client side:
1) Invocations from Silverlight look like this, this works if you are using the Channel Factory too.
var client = new MyWCFServiceClient();
client.GetPersonCompleted += client_GetPersonCompleted;
client.ClientCredentials.UserName.UserName = username;
client.ClientCredentials.UserName.Password = password;
client.GetPersonAsync();
2) return values from server will have an Error property if the login fails. If the user lookup fails, the error is something confusing like “at least one security token could not be validated”. Since your server side code is not able to re-wrap this (as it's all happening in the web.config) it is better for your client code to catch System.ServiceModel.Security.MessageSecurityException and interpret it as a login failure, or check the InnerException message to make sure it is the "security token" thing.
void client_GetPersonCompleted(object sender, GetPersonCompletedEventArgs e)
{
if (e.Error == null)
{
// do stuff with e.Result;
}
if (e.Error is MessageSecurityException)
{
// Your login did not work
}
}
Server side:
1) The WCF service class must have
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] or Required
2) you have to set up your LDS instance with SSL enabled, which is tricky. See: h't't'p://erlend.oftedal.no/blog/?blogid=7
3) web config - need to:
Add the LDS connection string
Add ActiveDirectoryMembershipProvider
Add <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
change your custom binding to include <security authenticationMode="UserNameOverTransport"/> see http://msdn.microsoft.com/en-us/library/dd833059(VS.95).aspx
Example:
<configuration>
<connectionStrings>
<add name="ADConnectionString" connectionString="LDAP://myserver:[SSL port number]/[where your user are in LDS, in my case: ‘OU=ADAM Users,O=Microsoft,C=US’]" />
</connectionStrings>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<membership defaultProvider="MyActiveDirectoryMembershipProvider">
<providers>
<add
name="MyActiveDirectoryMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="ADConnectionString"
connectionUsername="[domain]\[username]"
connectionPassword="[plain text windows password]"
connectionProtection="Secure"
/>
</providers>
</membership>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceBehaviour">
<serviceMetadata
httpsGetEnabled="true"
httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<userNameAuthentication
userNamePasswordValidationMode="MembershipProvider"
membershipProviderName="MyActiveDirectoryMembershipProvider"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<customBinding>
<binding name="myCustomBinding">
<security authenticationMode="UserNameOverTransport"/>
<!-- <binaryMessageEncoding /> this is optional, but good for performance-->
<httpsTransport />
</binding>
</customBinding>
</bindings>
<services>
<service name="MessageBasedSecurity.Web.MyWCFService" behaviorConfiguration="MyServiceBehaviour">
<endpoint address="" binding="customBinding" bindingConfiguration="myCustomBinding"
contract="MessageBasedSecurity.Web.MyWCFService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
</configuration>
I hope this helps someone. I welcome comments/improvements.