Blog Home  Home Feed your aggregator (RSS 2.0)  
Scott Klueppel's Blog - WCF
.NET Discourse
 
# Sunday, August 31, 2008

Why clutter your inbox with error messages? Why make special code provisions for users to receive error messages via email? Why not log your error messages and have users subscribe to receive them in their favorite RSS aggregator?

If you are logging your exceptions already, you may find it easier to provide a syndication service. The process is ridiculously simple, and starts by creating a new project using the "Syndication Service Library" template. This template creates everything for you. All you need to do now is fill the SyndicationFeed with SyndicationItem objects.

Add a new class file called Feeds.cs:

 

    1 using System;

    2 using System.Linq;

    3 using System.ServiceModel;

    4 using System.ServiceModel.Syndication;

    5 using System.ServiceModel.Web;

    6 

    7 namespace SyndicationService

    8 {

    9     [ServiceContract]

   10     [ServiceKnownType(typeof(Atom10FeedFormatter))]

   11     [ServiceKnownType(typeof(Rss20FeedFormatter))]

   12     public interface IFeeds

   13     {

   14         [OperationContract]

   15         [WebGet(UriTemplate = "{type}?env={env}&app={app}", BodyStyle = WebMessageBodyStyle.Bare)]

   16         SyndicationFeedFormatter CreateFeed(string type, string env, string app);

   17     }

   18 

   19     public class Feeds : IFeeds

   20     {

   21         public SyndicationFeedFormatter CreateFeed(string type, string env, string app)

   22         {

   23             SyndicationFeed feed = CreateSyndicationFeed(type, env, app);

   24 

   25             // Return ATOM or RSS based on query string

   26             // rss -> http://localhost:8000/Feeds/Errors?env=Production&app=MyAppName

   27             // atom -> http://localhost:8000/Feeds/Errors?env=Production&app=MyAppName&format=atom

   28             string query = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["format"];

   29             SyndicationFeedFormatter formatter = null;

   30             if (query == "atom")

   31             {

   32                 formatter = new Atom10FeedFormatter(feed);

   33             }

   34             else

   35             {

   36                 formatter = new Rss20FeedFormatter(feed);

   37             }

   38 

   39             return formatter;

   40         }

   41 

   42         private static SyndicationFeed CreateSyndicationFeed(string type, string env, string app)

   43         {

   44             SyndicationFeed feed;

   45             switch (type.ToLower())

   46             {

   47                 case "errors":

   48                     feed = CreateErrorsFeed(type, env, app);

   49                     break;

   50                 default:

   51                     feed = new SyndicationFeed(

   52                         String.Format("Feed is unavailable - Type: {0} / Environment: {1} / Application: {2}",

   53                         type, env, app), null, null);

   54                     break;

   55             }

   56             return feed;

   57         }

   58 

   59         private static SyndicationFeed CreateErrorsFeed(string type, string env, string app)

   60         {

   61             ApplicationLogDataContext db = new ApplicationLogDataContext();

   62 

   63             SyndicationFeed feed = new SyndicationFeed

   64             {

   65                 Title = new TextSyndicationContent(String.Format("{0} {1} {2}", env, app, type)),

   66                 Description = new TextSyndicationContent(

   67                     String.Format("Application error syndication for the {0} applicaiton ({1}).", app, env)),

   68                 Items = from e in db.Exceptions

   69                         where e.Environment == env && e.Application == app

   70                         select new SyndicationItem

   71                         {

   72                             Title = new TextSyndicationContent(e.Message),

   73                             Content = new TextSyndicationContent(e.StackTrace)

   74                         }

   75             };

   76             return feed;

   77         }

   78     }

   79 }

Modify the App.config file:

 

    1 <?xml version="1.0" encoding="utf-8" ?>

    2 <configuration>

    3     <configSections>

    4     </configSections>

    5     <connectionStrings>

    6         <add name="SyndicationService.Properties.Settings.ApplicationLogConnectionString"

    7             connectionString="Data Source=Scorpion;Initial Catalog=ApplicationLog;Integrated Security=True"

    8             providerName="System.Data.SqlClient" />

    9     </connectionStrings>

   10     <system.serviceModel>

   11         <services>

   12             <service name="SyndicationService.Feeds">

   13                 <host>

   14                     <baseAddresses>

   15                         <add baseAddress="http://localhost:8000/" />

   16                     </baseAddresses>

   17                 </host>

   18                 <endpoint contract="SyndicationService.IFeeds"

   19                           address="Feeds"

   20                           binding="webHttpBinding"

   21                           behaviorConfiguration="WebHttpBinding_Common"/>

   22             </service>

   23         </services>

   24         <behaviors>

   25             <endpointBehaviors>

   26                 <behavior name="WebHttpBinding_Common">

   27                     <webHttp/>

   28                 </behavior>

   29             </endpointBehaviors>

   30         </behaviors>

   31     </system.serviceModel>

   32 </configuration>

You will need to adjust your project's Debug options to have command arguments that look similar to the following to F5-debug your service.

"/client:iexplore.exe" "/clientArgs:http://localhost:8000/Feeds/Errors?env=Production&app=GeoTracker"

Press F5 to test it out.

Here is the IE7 RSS viewer:

IE7_RSS_Viewer

Here is your RSS aggregator viewing the same feed:

RSS_Aggregator

You will, of course, want to add some additional information to the content of your SyndidationItem, a bogus phrase works for this example.

Also, it is unusual that you would care to keep your exception details around for a long period of time. Since this is a syndicated feed of application errors, you should make special arrangements to archive or delete your exception log on a regular basis. This will not only keep your insert and select times low, but will also alleviate the burden placed on a new subscriber when all of the exceptions from the database appear at once. An alternative would also be to modify the LINQ in the code above to only bring back exceptions from the last 7-60 days depending on your counts. I already archive my exceptions to a master exception repository for all environments by way of an ETL job. This way I can report on my errors without disturbing the live environments too.

Sunday, August 31, 2008 3:37:38 AM (Eastern Standard Time, UTC-05:00)  #    Comments   .NET Framework | C# | LINQ | WCF  | 
# Saturday, August 23, 2008

Our friends at Microsoft may have slipped one in on us. After installing the 3.5 Framework Service Pack 1, it appears that you no longer need the [DataContract] or [DataMember] attributes on your DataContracts and DataMembers. I'm not sure what the motivation was for this "enhancement", but it caused some trouble for me the other day.

For this example I will be using the base project VS2008 gives you when you create a new WCF Service Library. I am simply adding a NestedType to the CompositeType given in the base project.

Before installing SP1, having code as it appears below would cause an error during Metadata Exchange that reads something like "Metadata contains a reference that cannot be resolved". Notice that CompositeType's NestedObject is marked as [DataMember] and also notice that the NestedType class is not marked as [DataContract] and has no [DataMember] attributes. Adding [DataContract] on NestedType and [DataMember] on IsVisible will clear this error and everything will work as expected. 

   24     [DataContract]

   25     public class CompositeType

   26     {

   27         bool boolValue = true;

   28         string stringValue = "Hello ";

   29         NestedType nestedObject = new NestedType();

   30 

   31         [DataMember]

   32         public bool BoolValue

   33         {

   34             get { return boolValue; }

   35             set { boolValue = value; }

   36         }

   37 

   38         [DataMember]

   39         public string StringValue

   40         {

   41             get { return stringValue; }

   42             set { stringValue = value; }

   43         }

   44 

   45         [DataMember]

   46         public NestedType NestedObject

   47         {

   48             get { return nestedObject; }

   49             set { nestedObject = value; }

   50         }

   51     }

   52 

   53     public class NestedType

   54     {

   55         bool isVisible = false;

   56 

   57         public bool IsVisible

   58         {

   59             get { return isVisible; }

   60             set { isVisible = value; }

   61         }

   62     }

 

The same code in use after SP1 will not cause this error. WCF will interpret from CompositeType's [DataContract] attribute and NestedObject's [DataMember] attribute that you meant to put [DataContract] on NestedType. So what's the big deal, right? WCF is doing me a solid by guessing at what I meant to do. To me, this violates the repeated opt-in theme present in WCF. For every other important decision, the developer must write code to opt-in to a feature. For example, TransactionFlow defaults to false so we don't use the client's incoming transaction with explicitly writing code that says to do so.

This is clearly not on the same level as TransactionFlow. But why does it assume something about my objects? Why does it assume that every member of my object should be a DataMember?

I noticed this new "feature" when troubleshooting some code that had different namespace names specified in the DataContract attribute. Since the NestedType did not have a [DataContract] attribute, the namespace was using the original namespace name. The equivalent of CompositeType came through correctly, but the NestedObject had no value.

Saturday, August 23, 2008 8:57:49 PM (Eastern Standard Time, UTC-05:00)  #    Comments   .NET Framework | C# | WCF  | 
# Wednesday, June 18, 2008

Juval Löwy mentioned the Microsoft Service Trace Viewer in a webcast today. If you ever wondered exactly what WCF does under all of those covers, check this out.

First things first. Enable tracing on the client and host applications using the WCF Configuration Editor. Enable the verbose trace level and check all of the listener settings. This will add all of the necessary <system.diagnostics> settings in your config file. The next time you start each of the applications, a .svclog file will be created that will be used by the Service Trace Viewer.

Start your host, start your client, run through the test cases that you want to analyze in the viewer. After your test run is complete, open the viewer, located at C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\SvcTraceViewer.exe. "Open" the host.svclog file, and then "Add" the client.svclog file. Both "Open" and "Add" are menu items under "File".

Start on the Activity tab, look through the host and client activities that occurred. Everything from ServiceHost construction through ServiceHost closing shows up. This is very cool, especially when analyzing the differences between different security, session, and reliability settings.

When you are done looking through the activities, check out the Graph tab. Here you can look at the interactions between the client and host, as well as looking at the details of each activity (at the top right). At the bottom right, you will also notice the formatted and xml details of this activity.

This is a very cool tool for both debugging and training. Below is my lame test projects, if you want to skip past the configuration and check out the tool. My .svclog files are located in the Client and Host folders.

SvtTest.zip (190.32 KB)

Enjoy! Thanks to Juval for the direction.

Wednesday, June 18, 2008 9:23:30 PM (Eastern Standard Time, UTC-05:00)  #    Comments   .NET Framework | C# | Dev Tools | Visual Studio | WCF  | 
# Wednesday, March 26, 2008

I was looking for guidance on this topic, and came up with nothing. I'm sure people are doing this, but can't find any info. For anyone looking like I was, here's how to do it.

It's much simpler than I imagined, thanks to WCF. You can programmatically create your endpoint, binding, and channel inside your service. This would require that the address be hard-coded and require a recompile to change the address or binding. As long as your host's app.config or web.config has a client endpoint specifying the contract, you don't have to go through all that work. Your service is simply a client of another service, so your code looks just like that of a client of your service. Furthermore, changing the address or binding is as simple as changing config file values.

Service code:

    1 using System;

    2 using System.ServiceModel;

    3 using DataContracts;

    4 namespace ServiceImplementation

    5 {

    6     [ServiceContract]

    7     public interface IEmailService

    8     {

    9         [OperationContract]

   10         void Send(DataContracts.MailMessage msg);

   11     }

   12 

   13     public class EmailService : IEmailService

   14     {

   15         [OperationBehavior]

   16         public void Send(DataContracts.MailMessage msg)

   17         {

   18             // Open client proxy for legacy web service

   19             using (LegacyEmailServiceClient proxy =

   20                 new LegacyEmailServiceClient())

   21             {

   22                 proxy.SendEmail(msg.To,

   23                     msg.CC,

   24                     msg.Bcc,

   25                     msg.Body,

   26                     msg.Attachments);

   27             }

   28         }

   29     }

   30 }

   31 



Host's app.config:

    1 <?xml version="1.0" encoding="utf-8"?>

    2 <configuration>

    3   <system.serviceModel>

    4     <bindings>

    5       <basicHttpBinding>

    6         <binding name="BasicHttpBinding_Common">

    7           <security mode="None"/>

    8         </binding>

    9       </basicHttpBinding>

   10       <netTcpBinding>

   11         <binding name="NetTcpBinding_Common">

   12           <security mode="None"/>

   13         </binding>

   14       </netTcpBinding>

   15     </bindings>

   16     <client>

   17       <endpoint address="http://www.gotjeep.net/legacy/email.asmx"

   18           binding="basicHttpBinding"

   19                 bindingConfiguration="BasicHttpBinding_Common"

   20           contract="LegacyEmailServiceClient"

   21                 name="LegacyEmailServiceClient" />

   22     </client>

   23     <services>

   24       <service name="ServiceImplementation.EmailService"

   25               behaviorConfiguration="returnFaults">

   26         <host>

   27           <baseAddresses>

   28             <add baseAddress="http://localhost:8080/EmailService" />

   29             <add baseAddress="net.tcp://localhost:8088/EmailService" />

   30           </baseAddresses>

   31         </host>

   32         <endpoint name="NetTcpBinding_EmailService"

   33                   binding="netTcpBinding"

   34                   bindingConfiguration="NetTcpBinding_Common"

   35                   contract="ServiceImplementation.IEmailService"/>

   36         <endpoint name="BasicHttpBinding_EmailService"

   37                   binding="basicHttpBinding"

   38                   bindingConfiguration="BasicHttpBinding_Common"

   39                   contract="ServiceImplementation.IEmailService"/>

   40       </service>

   41     </services>

   42     <behaviors>

   43       <serviceBehaviors>

   44         <behavior name="returnFaults" >

   45           <serviceMetadata httpGetEnabled="true" />

   46         </behavior>

   47       </serviceBehaviors>

   48     </behaviors>

   49   </system.serviceModel>

   50 </configuration>

Wednesday, March 26, 2008 9:08:46 PM (Eastern Standard Time, UTC-05:00)  #    Comments   .NET Framework | C# | WCF  | 
# Tuesday, March 25, 2008

If you haven't heard me praise Juval Löwy's book Programming WCF Services or Michele Leroux Bustamante's book Learning WCF: A Hands-on Guide yet... these books are the best WCF books available. Every question I have had has been answered by these two books. In fact, most of the forum and newsgroup resolutions out there come from one or both of these books. If you haven't already, check out their IDesign Code Library and and the IDesign WCF Coding Standard at www.IDesign.net.

OK, problem and resolution of the day... how do I generate a proxy file with the same collection class as my service implementation. My service uses List<T>. WCF converts this to be a more interoperable array of T. When you generate the proxy, it is generated with T[]. To be able to enjoy the same collection features as the service, you only need a few more parameters on svcutil.

    7     [ServiceContract]

    8     public interface IEmailService

    9     {

   10         [OperationContract]

   11         string MyOperation1(string myValue);

   12 

   13         [OperationContract]

   14         List<String> MyOperation2(string myValue);

   15     }

Example 1: Generate proxy with T[]

svcutil http://localhost:8080/EmailService /out:EmailServiceProxy.cs /noconfig

[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IEmailService/MyOperation2", ReplyAction="http://tempuri.org/IEmailService/MyOperation2Response")]
string[] MyOperation2(string myValue);

Example 2: Generate proxy with List<T>

svcutil http://localhost:8080/EmailService /out:EmailServiceProxy.cs /noconfig /ct:System.Collections.Generic.List`1

[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IEmailService/MyOperation2", ReplyAction="http://tempuri.org/IEmailService/MyOperation2Response")]
System.Collections.Generic.
List<string> MyOperation2(string myValue);

Tuesday, March 25, 2008 9:22:22 PM (Eastern Standard Time, UTC-05:00)  #    Comments   .NET Framework | WCF | C#  | 
# Monday, March 24, 2008

Before you try running a Console Application or Windows Service application that hosts a WCF service with basicHttpBinding or wsHttpBinding, see this MSDN article about "Configuring HTTP and HTTPS". If you are WAS-hosted or Web App-hosted, the urlacl entries are made on your behalf.

You can view the current entries with "netsh http show urlacl". To make the changes, you'll need to "Run as Administrator" when going into your Command Prompt.

I decided to use the following command:

netsh http add urlacl url=http://+:8080/ user=\Everyone

You should adjust the ports and/or path as necessary for your situation:

netsh http add urlacl url=http://+:8080/MyConsoleAppHostedService user=DOMAIN\user

netsh http add urlacl url=http://+:8091/MyWindowsServiceHostedService user=\SYSTEM

Monday, March 24, 2008 8:03:15 PM (Eastern Standard Time, UTC-05:00)  #    Comments   .NET Framework | WCF  | 
# Saturday, March 22, 2008

WCF is seamless, powerful, and (YES) interoperable. Here is a quick walkthrough detailing steps to call your legacy PHP web services from a WCF client. All of the binding types and security options are, of course, not available. But for those of you following the old "security through obscurity" model, this will work fine.

The procedure is just as simple as a WCF-WCF call:
  1. Create client proxy
  2. Add client endpoint to your client config file
  3. Write your proxy-consuming client code

Create the client proxy:

Nusoap provides a similar UI when navigating to your PHP web service to that provided by ASMX services. You can view the WSDL, and copy the URL for svcutil.exe.

From the Visual Studio 2005 Command Prompt, type:

svcutil <url> /out:<name>Proxy.cs /noconfig
svcutil http://gotjeep.net/services/ApproachAngleService.php?wsdl /out:ApproachAngleServiceProxy.cs /noconfig

This generates a file named ApproachAngleServiceProxy.cs.

    1 //------------------------------------------------------------------------------

    2 // <auto-generated>

    3 //    This code was generated by a tool.

    4 //    Runtime Version:2.0.50727.1433

    5 //

    6 //    Changes to this file may cause incorrect behavior and will be lost if

    7 //    the code is regenerated.

    8 // </auto-generated>

    9 //------------------------------------------------------------------------------

   10 

   11 

   12 

   13 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]

   14 [System.ServiceModel.ServiceContractAttribute(Namespace="http://www.gotjeep.net/tech", ConfigurationName="GotJeepApproachAngleCalculatorPortType")]

   15 public interface GotJeepApproachAngleCalculatorPortType

   16 {

   17 

   18     [System.ServiceModel.OperationContractAttribute(Action="http://www.gotjeep.net/services/approachAngleService.php/CalculateApproachAngle", ReplyAction="*")]

   19     [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, Use=System.ServiceModel.OperationFormatUse.Encoded)]