WCF 4.0 comes with whole lot of new features primarily aimed at enhancing Rapid application development.Features like svc less WCF service,enhanced and simplified config makes it even more productive to develop and deploy WCF services. One such feature is the WCF 4.0 routing service.
Lets assume a scenario where we have a customer processing service. We have one service that processes legacy customers(customers that are already part of business process). And another service that processes entirely new customers. In .NET 3.5, a WCF client would have to call the appropriate service based on the customer type. So the clients needs to aware of two endpoints. Teh service owner needs to apply security,configuration,transaction supports to each service individually.
To relieve the WCF clients pain of deciding the service to be called, we can have a WCF intermediary service whose endpoint is exposed to the client and the remaining services endpoints are exposed only to this intermediary service.But this requires the service developer to write an intermediary service which takes a lot of plumbing and therefore a lot of effort. Check this article by Michele Leroux Bustamante. WCF Routing .
But thats in .NET 3.5. Come WCF 4.0 and looks what feature has been added. We have something called as WCF routing service which takes care of the rotuing. All you need to code is the logic to route.
Our solution has five projects as shown below.One class library to hold the re usable contracts. Two services(new customer service and legacy customer) that use the contracts in the contract librayr project. One routin service and the console client. The WCF services have following service implementation
public string ProcessCustomer(CustomerInformation info)
{
return "Legacy customer processed";
}
Nothing fancy in there. Now lets turn our attention to the routing service.Remove all the auto generated stuff except for the svc file and the config. Even the code behind can deleted. In teh svc make sure your mark up is like this
And now to the web.config. Add the actual endpoint addresses of our legacy and new customer service.
<client>
<endpoint address="http://localhost:62662/NewCustomer.svc" binding="basicHttpBinding" contract="*" name="NewCustomerEP"></endpoint>
<endpoint address="http://localhost:62656/LegacyCustomer.svc" binding="basicHttpBinding" contract="*" name="LegacyCustomerEP"></endpoint>
</client>
Next we have to inform the routing logic and set up the routing filters as below.
<routing>
<namespaceTable>
<add namespace="http://schemas.datacontract.org/2004/07/ContractLibrary" prefix="custom"/>
</namespaceTable>
<filterTables>
<filterTable name="filterTable1">
<add filterName="LegacyCustomerFilter" priority="0" endpointName="LegacyCustomerEP" />
<add filterName="NewCustomerFilter" priority="0" endpointName="NewCustomerEP" />
</filterTable>
</filterTables>
<filters>
<filter name="LegacyCustomerFilter" filterType="XPath" filterData="//custom:CustomerType='LEGACY'"/>
<filter name="NewCustomerFilter" filterType="XPath" filterData="//custom:CustomerType='NEW'"/>
</filters>
</routing>
The above filter setup says that if CustomerType element in the message equals 'LEGACY' then route it to legacy service endpoint and if its 'NEW' then route it to new service endpoint.This is simplified using XPATH which is just one of the otions to filter. Its that simple!!But yes a bit of work might be needed to identify the XPATH query.
The Source Code is here
Showing posts with label WCF. Show all posts
Showing posts with label WCF. Show all posts
Sunday, April 18, 2010
Sunday, November 1, 2009
Hosting WCF Service in Windows forms application and Sendtimeout exception
I was entrusted with a development of a POC(proof of concept) for an occasionally connected service. The client was a smart client application and the service was written in WCF. The idea was to have a online service when the machine was connected to a network and to use a self hosted service when not connected to the network. The sync between online and offline data would be take care by ado.net sync services.
The idea was to host the service in the shell form. If you know or heard about CAB SCSF then you are sure to know about shell form. Right so i have my offline service implementation and the contracts were in an assembly to enable re use between offline and online services.
This is what i did to host my service
public Form1()
{
InitializeComponent();
ServiceHost host = new ServiceHost(typeof(Service1));
host.Open();
}
I hosted my service in the form's constructor. I called my service operation in the form_load event
ChannelFactory factory = new ChannelFactory(new WSHttpBinding(), new EndpointAddress("http://localhost:8731/Design_Time_Addresses/WindowsFormsHost/Service1/"));
IService1 proxy = factory.CreateChannel();
proxy.DoWork();
This is the exception i got

Seems quite wierd. After hosting it in the current winform i tried accesing it from another winform solution and i was able to access the service. Ok so, i was hosting the service in proc which failed. So my service is fine.
I was able to nail the problem after a long tiresome googlejob!! The more I looked the more it became painfully obvious that I had a threading deadlock issue. The UseSynchronizationContext from the ServiceBehavior attribute in WCF is used to determine which thread your service will execute on. Basically System.Threading.SynchronizationContext.Current is read and cached so that when a request comes to the service, the host can marshal the request onto the thread that the host was created on using the cached SynchronizationContext.
As i mentioned earlier the timeout exception was due to a deadlock. The reason for this is that the default value of the UseSynchronizationContext is true and so when you create the ServiceHost on the UI thread of the winform application, then the current synchronization context is a DispatcherSynchronizationContext which holds a reference to a System.Windows.Threading.Dispatcher object which then holds a reference to the current thread. The DispatcherSynchronizationContext will then be used when a request comes in to marshal requests onto the UI thread. But if you are calling the service from the UI thread then you have a deadlock when it tries to do this!!
Phew!!! There are two ways to fix this issue.
1.)First one is to declare [CallbackBehavior(UseSynchronizationContext = false)] on the service class. Set that to false on your service and the service will create it's own SynchronizationContext and your client will no longer block when hosting the service in process.
2.)Host the servie before form constructor is invoked.
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ServiceHost host = new ServiceHost(typeof(Service1));
host.Open();
Application.Run(new Form1());
}
The idea was to host the service in the shell form. If you know or heard about CAB SCSF then you are sure to know about shell form. Right so i have my offline service implementation and the contracts were in an assembly to enable re use between offline and online services.
This is what i did to host my service
public Form1()
{
InitializeComponent();
ServiceHost host = new ServiceHost(typeof(Service1));
host.Open();
}
I hosted my service in the form's constructor. I called my service operation in the form_load event
ChannelFactory
IService1 proxy = factory.CreateChannel();
proxy.DoWork();
This is the exception i got
Seems quite wierd. After hosting it in the current winform i tried accesing it from another winform solution and i was able to access the service. Ok so, i was hosting the service in proc which failed. So my service is fine.
I was able to nail the problem after a long tiresome googlejob!! The more I looked the more it became painfully obvious that I had a threading deadlock issue. The UseSynchronizationContext from the ServiceBehavior attribute in WCF is used to determine which thread your service will execute on. Basically System.Threading.SynchronizationContext.Current is read and cached so that when a request comes to the service, the host can marshal the request onto the thread that the host was created on using the cached SynchronizationContext.
As i mentioned earlier the timeout exception was due to a deadlock. The reason for this is that the default value of the UseSynchronizationContext is true and so when you create the ServiceHost on the UI thread of the winform application, then the current synchronization context is a DispatcherSynchronizationContext which holds a reference to a System.Windows.Threading.Dispatcher object which then holds a reference to the current thread. The DispatcherSynchronizationContext will then be used when a request comes in to marshal requests onto the UI thread. But if you are calling the service from the UI thread then you have a deadlock when it tries to do this!!
Phew!!! There are two ways to fix this issue.
1.)First one is to declare [CallbackBehavior(UseSynchronizationContext = false)] on the service class. Set that to false on your service and the service will create it's own SynchronizationContext and your client will no longer block when hosting the service in process.
2.)Host the servie before form constructor is invoked.
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ServiceHost host = new ServiceHost(typeof(Service1));
host.Open();
Application.Run(new Form1());
}
Monday, October 26, 2009
Flowing ASP.NET Forms authentication cookie to WCF
This being my first blog,I would like to start off with something that I love. WCF!!!This is about flowing ASP.NET Forms authentication to WCF service. Once successfully authenticated, forms authentication data is stored as an encrypted cookie. And since the encryption is machine dependent its important to have the validationKey and decryptionKey same in both the web.configs(web app and WCF app).
Check this MSDN link to know more about configuring machineKey for asp.net forms authentication.
1.)Create a validationkey and decryptionkey. Run the below program two times. The value generated first time can be used as the validationKey and the second one machineKey in the web.configs.
static void Main(string[] args)
{
int len = 48;
byte[] buff = new byte[len / 2];
RNGCryptoServiceProvider rng = new
RNGCryptoServiceProvider();
rng.GetBytes(buff);
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < buff.Length; i++)
sb.Append(string.Format("{0:X2}", buff[i]));
Console.WriteLine(sb);
}
2.)Configure the web.configs(wcf and web app) to use the generated keys as follows.
<system.web/>
<span style="font-weight:bold;"/> <machineKey validationKey="D4E763ABFD89DAAD50620E89E11FFAB7AD94AF9EDE264526" decryptionKey="5F0A4D7396E35CF534F0B404F481CA70C2C5A43071729BE4" decryption="3DES"/>
</span>
3.)Configure the WCF service to run in ASP NET compatibility mode.
This is to be done in two place. One in wcf service web.config as follows
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
And
in the service behaviour
[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1 : IService1
{
4.)Configure forms authentication in asp.net config
<authentication mode="Forms">
<forms name="appNameAuth" path="/" loginUrl="login.aspx" protection="All" timeout="30">
<credentials passwordFormat="Clear">
<user name="jeff" password="test" />
<user name="mike" password="test" />
</credentials>
</forms>
</authentication>
<authorization>
<deny users="?" />
</authorization>
5.)In login.aspx
if (FormsAuthentication.Authenticate(txtUser.Text, txtPassword.Text))
FormsAuthentication.RedirectFromLoginPage(txtUser.Text, chkPersistLogin.Checked);
else
ErrorMessage.InnerHtml = "Something went wrong... please re-enter your credentials...";
This sets the authentication cookie on successful login.
6.)Now send the cookie in WCF request http header
HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers.Add(HttpRequestHeader.Cookie, FormsAuthentication.GetAuthCookie(User.Identity.Name, false).Value);
ServiceReference1.Service1Client serviceClient = new WebApplication1.ServiceReference1.Service1Client();
using (OperationContextScope scope = new OperationContextScope(serviceClient.InnerChannel))
{
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
string s =serviceClient.GetData(2);
}
7.)At the service side you can read the cookie inside the operation as follows
HttpRequestMessageProperty g = OperationContext.Current.IncomingMessageProperties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
string s = g.Headers.Get(HttpRequestHeader.Cookie.ToString());
FormsAuthenticationTicket tick = FormsAuthentication.Decrypt(s);
Working sample can be downloaded from here
Let me know on any issues. Happy coding :)
Check this MSDN link to know more about configuring machineKey for asp.net forms authentication.
1.)Create a validationkey and decryptionkey. Run the below program two times. The value generated first time can be used as the validationKey and the second one machineKey in the web.configs.
static void Main(string[] args)
{
int len = 48;
byte[] buff = new byte[len / 2];
RNGCryptoServiceProvider rng = new
RNGCryptoServiceProvider();
rng.GetBytes(buff);
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < buff.Length; i++)
sb.Append(string.Format("{0:X2}", buff[i]));
Console.WriteLine(sb);
}
2.)Configure the web.configs(wcf and web app) to use the generated keys as follows.
<system.web/>
<span style="font-weight:bold;"/> <machineKey validationKey="D4E763ABFD89DAAD50620E89E11FFAB7AD94AF9EDE264526" decryptionKey="5F0A4D7396E35CF534F0B404F481CA70C2C5A43071729BE4" decryption="3DES"/>
</span>
3.)Configure the WCF service to run in ASP NET compatibility mode.
This is to be done in two place. One in wcf service web.config as follows
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
And
in the service behaviour
[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1 : IService1
{
4.)Configure forms authentication in asp.net config
<authentication mode="Forms">
<forms name="appNameAuth" path="/" loginUrl="login.aspx" protection="All" timeout="30">
<credentials passwordFormat="Clear">
<user name="jeff" password="test" />
<user name="mike" password="test" />
</credentials>
</forms>
</authentication>
<authorization>
<deny users="?" />
</authorization>
5.)In login.aspx
if (FormsAuthentication.Authenticate(txtUser.Text, txtPassword.Text))
FormsAuthentication.RedirectFromLoginPage(txtUser.Text, chkPersistLogin.Checked);
else
ErrorMessage.InnerHtml = "Something went wrong... please re-enter your credentials...";
This sets the authentication cookie on successful login.
6.)Now send the cookie in WCF request http header
HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers.Add(HttpRequestHeader.Cookie, FormsAuthentication.GetAuthCookie(User.Identity.Name, false).Value);
ServiceReference1.Service1Client serviceClient = new WebApplication1.ServiceReference1.Service1Client();
using (OperationContextScope scope = new OperationContextScope(serviceClient.InnerChannel))
{
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
string s =serviceClient.GetData(2);
}
7.)At the service side you can read the cookie inside the operation as follows
HttpRequestMessageProperty g = OperationContext.Current.IncomingMessageProperties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
string s = g.Headers.Get(HttpRequestHeader.Cookie.ToString());
FormsAuthenticationTicket tick = FormsAuthentication.Decrypt(s);
Working sample can be downloaded from here
Let me know on any issues. Happy coding :)
Subscribe to:
Posts (Atom)
