Contact
Send mail to the author(s) Email Me

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Sign In
Navigation

Tag Cloud
.NET Framework (33) AJAX (9) ASP.NET (16) ASP.NET MVC (3) Azure (1) C# (35) Cloud (3) Database (7) Dev Community (2) Dev Tools (7) Enterprise Library (2) Extensions (1) Futures (2) General (6) IIS (1) Infrastructure (1) Javascript (7) LINQ (2) Mobile (1) MSDTC (6) Queuing (1) Quotes (5) SQL (5) Transactions (6) Visual Studio (3) WAS (2) WCF (24) WIF (1)

Archive
<July 2010>
SunMonTueWedThuFriSat
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

Categories

Blogroll
Home Feed your aggregator (RSS 2.0)
# Monday, July 26, 2010

In .NET 1.1, I tried the original MS Data Access Application Block’s SqlHelper (you can still download it here). It was great for most of the common uses, but was lacking in some areas. The consuming code looked sloppy and encouraged blind faith that database objects never changed. It also didn’t support transactions as I would have liked, and didn’t support my obsession with custom entities. I started out writing an extension library that wrapped SqlHelper, but that felt very wrong… wrapping the ADO.NET wrapper (SqlHelper). I ended up writing my own version of SqlHelper called SqlHelper (nice name, eh?). You see, at this time I was getting over a bad relationship with a series of ORM products that had a negative effect on my productivity. I decided to revolt with good ol’ fashion data access methods that have never let us down.

The only thing worse than my ORM experience was the disgusting over-use of DataSet and DataTable. For my dollar, DataReader is where it’s at. I agree that using the reader is slightly more dangerous in the hands of an inexperienced or inattentive developer (did you know you have to close the reader when you’re done with it??). Nothing can compare with the speed and flexibility of the reader, which is why DataSet and DataAdapter use it at their core. If you are working with custom entities, instead of DataSets and DataTables, you would be crazy to not use the DataReader.

My SqlHelper worked in conjunction with my DataAccessLayer class that defined a few delegates that made reader-to-object-mapping a simple task.  Once the mapping methods were written to be used with the delegates, which returned object or System.Collections.CollectionBase because we did not yet have generics (can you imagine??), you simply called the SqlHelper to do all of the hard work. SqlHelper did not implement all of the craziness that the original version contained. It was a short 450 lines of code that did nothing but access data in a safe and reliable way. In the example below, we have the GenerateDocumentFromReader method that is used by the GenerateObjectFromReader delegate. When SqlHelper.ExecuteReaderCmd is called, the delegate is passed in to map the reader results to my object… in this case a Document.

// Object generation method 
private static object GenerateDocumentFromReader(IDataReader returnData) 
{
     Document document = new Document();
     if (returnData.Read())
     {
         document = new Document(
             (int)returnData["DocumentId"],
             (byte[])returnData["DocumentBinary"],
             returnData["FileName"].ToString(),
             returnData["Description"].ToString(),
             returnData["ContentType"].ToString(),
             (int)returnData["FileSize"],
             returnData["MD5Sum"].ToString(),
             (bool) returnData["EnabledInd"],
             (int)returnData["CreatorEmpId"],
             Convert.ToDateTime(returnData["CreateDt"]),
             (int)returnData["LastUpdateEmpId"],
             Convert.ToDateTime(returnData["LastUpdateDt"]));
     }     return document;
} 
public static Document GetDocumentByDocumentId(int documentId)
{
     SqlCommand sqlCmd = new SqlCommand();
     SqlHelper.SetCommandArguments(sqlCmd, CommandType.StoredProcedure, "usp_Document_GetDocumentByDocumentId");
     SqlHelper.AddParameterToSqlCommand(sqlCmd, "@DocumentId", SqlDbType.Int, 0, ParameterDirection.Input, documentId);
     DataAccessLayer.GenerateObjectFromReader gofr = new DataAccessLayer.GenerateObjectFromReader(GenerateDocumentFromReader);
     Document document = SqlHelper.ExecuteReaderCmd(sqlCmd, gofr) as Document;
     return document;
}

This worked wonderfully for years. After converting, I couldn’t imagine a project that used ORM, DataSets, or DataTables again. I’ve been on many 1.1 projects since writing my SqlHelper in 2004, and I have successfully converted them all. In early 2006, MS graced us with .NET 2.0. Generics, System.Transactions, and partial classes changed my life. In my first few exposures to generics, like Vinay “the Generic Guy” Ahuja’s 2005 Jax Code Camp presentation and Juval “My Hero” Lowy’s MSDN article “An Introduction to Generics”, I listened/read and pondered the millions of uses of generics. I adapted my SqlHelper heavily to use these new technologies and morphed it into something else that closely represented the newest version of the DAAB, Enterprise Library 3.

By this point, I wanted to convert to Enterprise Library. It was far better than the simple SqlHelper. It had better transaction support, though I don’t know if that included System.Transactions. I could have put my object generation extensions on top of it and it would have worked well for years. On home projects I had already converted to use EntLib. At work I was not so lucky. The deep stack trace when something went wrong scared everyone, and that is still a fear for those starting out in EntLib today. To ease the fears, I just created my replacement to SqlHelper… the Database class.

I used a lot of the same naming conventions as Enterprise Library. In fact, much of the consuming code was nearly identical (except for the fact that it did not implement the provider pattern and worked only with SQL Server). This was in anticipation of a quick adoption of Enterprise Library 3 in the workplace. Kind of a “see… not so bad” move on my part. Just like EntLib, you created a Database class using the DatabaseFactory that used your default connection string key. Commands and parameters were created and added with methods off of the Database class. Aside from the SqlCommand/DbCommand, everything looked and felt the same, but came in a small file with only 490 lines of code instead of 5 or more projects with 490 files. Using it felt the same, too. Only my object/collection generation extensions looked different from the standard reader, scalar, dataset routines. Below is the same code from above using the Database class and related classes to create a Document from a reader.

// Object generation method
private static Document GenerateDocumentFromReader(IDataReader returnData)
{
     Document document = new Document();
     if (returnData.Read())
     {
         document = new Document(
             GetIntFromReader(returnData, "DocumentId"),
             GetIntFromReader(returnData, "DocumentTypeId"),
             GetStringFromReader(returnData, "DocumentTypeName"),
             GetByteArrayFromReader(returnData, "DocumentBinary"),
             GetStringFromReader(returnData, "FileName"),
             GetStringFromReader(returnData, "Description"),
             GetStringFromReader(returnData, "ContentType"),
             GetIntFromReader(returnData, "FileSize"),
             GetStringFromReader(returnData, "MD5Sum"),
             GetStringFromReader(returnData, "CreatorEmpID"),
             GetDateTimeFromReader(returnData, "CreateDt"),
             GetStringFromReader(returnData, "LastUpdateEmpID"),
             GetDateTimeFromReader(returnData, "LastUpdateDt"));
     }
     return document;
} 
public static Document GetDocumentByDocumentId(int documentId)
{
     Database db = DatabaseFactory.CreateDatabase(AppSettings.ConnectionStringKey);
     SqlCommand sqlCmd = db.GetStoredProcCommand("usp_Document_GetDocumentByDocumentId");
     db.AddInParameter(sqlCmd, "DocumentId", SqlDbType.Int, documentId);
     GenerateObjectFromReader<Document> gofr = new GenerateObjectFromReader<Document>(GenerateDocumentFromReader);
     Document document = CreateObjectFromDatabase<Document>(db, sqlCmd, gofr);
     return document;
}

This, too, worked great for years. Other than a brief period in 2007 when I tried to wrap all of my data access code with WCF services, .NET 3.0 came and went with no changes to my data access methodology. In late 2007, I had lost all love of my SqlHelper and my Database/DataAccessLayer classes. With .NET 3.5 and Enterprise Library 4.0, I no longer felt the need to roll my own. .NET now had extension methods for me to extend Enterprise Library however I pleased. Enterprise Library supported System.Transactions making its use a dream if behind a WCF service that allowed transaction flow. With a succinct 190 lines of extension code, I had it made in the shade with Enterprise Library 4.0. In fact, I haven’t used anything since.

The consuming code was almost exactly the same. You’ll notice the SqlCommand has changed to DbCommand. The SqlDbType has changed to DbType. Other than that, it feels and works the same.

// Object generation method
private static Document GenerateDocumentFromReader(IDataReader returnData)
{
     Document document = new Document();
     if (returnData.Read())
     {
         document = new Document(
             returnData.GetInt32("DocumentId"),
             returnData.GetInt32("DocumentTypeId"),
             returnData.GetString("DocumentTypeName"),
             returnData.GetByteArray("DocumentBinary"),
             returnData.GetString("FileName"),
             returnData.GetString("Description"),
             returnData.GetString("ContentType"),
             returnData.GetInt32("FileSize"),
             returnData.GetString("MD5Sum"),
             returnData.GetString("CreatorEmpID"),
             returnData.GetDateTime("CreateDt"),
             returnData.GetString("LastUpdateEmpID"),
             returnData.GetDateTime("LastUpdateDt"));
     }
     return document;
}
public static Document GetDocumentByDocumentID(int documentId)
{
     Database db = DatabaseFactory.CreateDatabase();
     DbCommand cmd = db.GetStoredProcCommand("usp_Document_GetDocumentByDocumentId");
     db.AddInParameter(cmd, "DocumentID", DbType.Int32, documentId);
     GenerateObjectFromReader<Document> gofr = new GenerateObjectFromReader<Document>(GenerateDocumentFromReader);
     Document document = db.CreateObject<Document>(cmd, gofr);
     return document;
}

With a full suite of unit test projects available for download with the Enterprise Library source files, the fear should be abated for the remaining holdouts. Getting started is as easy as including two DLL references, and adding 5 lines of config. You can’t beat that!

I downloaded Enterprise Library 5 last week. I’ve been making use of new features such as result set mapping (eliminating the need for my object generation extensions), parameter mapping, and accessors that bring them all together. There’s a bunch of inversion of control features in place as well. I think I’ll be quite comfortable in my new EntLib5 home.

Monday, July 26, 2010 10:32:07 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   C# | Database | Enterprise Library | Extensions | SQL  | 
# Saturday, May 08, 2010

Despite its pitiful adoption in the developer community, I am implementing Transactional NTFS (TxF) transactions using the Microsoft.KtmIntegration.TransactedFile class. This allows me to reap the benefits of TransactionScope and distributed transactions for file operations (e.g. creates, updates, deletes). This is the only missing piece for typical transactional business applications. With the “KTM” and “KtmRm for Distributed Transactions” services, available only on Vista, Windows 7, and Windows Server 2008, file operations will roll back if the TransactionScope is not completed.

There’s just one problem… Transactional NTFS does not work with file shares. I can’t remember the last time I put a “C:\FileStore” reference in a config file. A friendly share like “\\server\FileStore” is always preferred, especially since DFS came about. Attempting to use a share results in the following error message:

The remote server or share does not support transacted file operations

Don’t read this as “your remote server” or “your remote share”, but rather “all remote servers and shares”. As mentioned in this MSDN article, TxF is not supported by the CIFS/SMB protocols. The error was probably written with the expectation that one day some remote servers and shares would support TxF. I emailed Microsoft about it and received a response fairly quickly. The response was simply:

“We understand the need and have plans to eventually support TxF over SMB2, but we’re not there yet and are not ready to announce if or when this will be supported. When it is the documentation will be updated.”

I’m not getting my hopes up, but Windows Server 2011 looks to be our only hope before .NET changes beyond recognition and TxF is a distant memory. Until then, I wrapped up all of my TxF code in a WCF service and install that service on the server with the FileStore folder.

MSDN article – When to Use Transactional NTFS

      http://msdn.microsoft.com/en-us/library/aa365738(v=VS.85).aspx

TxF Sandbox – Sample Projects (including Microsoft.KtmIntegration.TransactedFile)

      TxFSandbox.zip

Saturday, May 08, 2010 10:32:25 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   .NET Framework | C# | MSDTC | Transactions | WCF  | 
# Wednesday, April 21, 2010

The sole 2010 offering in the USA of IDesign’s Architect’s Master Class conducted by the man himself, Juval Lowy, is only a few weeks away. I checked in at the IDesign web site, and found some updates the world needs to see.

If you want to learn something new every day, start at the top of the IDesign Code Library and step through one example each day. Be careful, you might need to re-write every line of code you’ve ever written.

Wednesday, April 21, 2010 7:56:04 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   .NET Framework | Azure | C# | Cloud | WCF  | 
# Sunday, January 31, 2010

I’m not sure if what I’m doing is actually the right way to create a “user control” in ASP.NET MVC, but it’s worth sharing this tidbit either way. Instead of using a MVC View User Control to create a hidden field, a text box, two anchors, and three JavaScript functions, I chose to put it all in a HtmlHelper in which I write out the HTML and JavaScript myself. Everything worked fine except the almost magical auto-repopulating of the hidden and text fields after a post that didn’t work as expected as in a typical MVC View Page.

The situation: I have a page that needs to be called as a popup from many pages in my MVC application. The page allows single or multiple selection of “items” driven by an XML file. In the event that one day, almost always immediately, I have two or more of these “controls” on one view page, I need the two fields and the three JavaScript functions to have unique names so they don’t cross paths and cause unexpected behavior. I had an ASP.NET User Control to do this in plain old ASP.NET (POAN) since v1.1, and I can’t live without it.

The confusion: If I were to place the hidden, textbox, anchors, and JavaScript functions directly in the calling page, something magical happens after a post. If the controls had values before the post, they appear to magically retain there values after the post. It wasn’t until a colleague of mine, Sat, and I dug into Reflector for a while did we realize what was happening. Html.TextBox, Html.Hidden, and others all do something similar to auto-magically re-populate their values after the post. Since I’m writing out my fields as <input type=”hidden”/> and <input type=”text”/>, the magic doesn’t happen.

      NOTE: The magic will also not happen if you just write <input type=”text”/> on the page. It only happens if you use Html.TextBox.

The solution: I am still new to MVC and still trying to wrap my head around the “right way” to do things. Reflector showed that the HtmlHelpers all looked at the ModelState in the ViewData before rendering their HTML. They looked for their value by key (key being the control/tag name), and, if present, used that as the control/tag’s value. Bing! Maybe I should do the same thing. So just before I go to town with TagBuilder to assemble my controls/tags, I look in the ViewData’s ModelState for my value. If it is there, it must have been posted there by me (my control).

   48         UrlHelper urlHelper = new UrlHelper(helper.ViewContext.RequestContext);

   49         string textValue = null;

   50         ModelState state;

   51 

   52         if (helper.ViewData.ModelState.TryGetValue(textFieldName, out state))

   53         {

   54             textValue = state.Value.AttemptedValue;

   55         }


Works like a charm! Now my hidden, textbox, two anchors, and three JavaScript functions are bundled nicely inside of an HtmlHelper class that looks and feels like I’m using a built-in ASP.NET MVC HtmlHelper class. Most importantly, I have the pleasure of typing only this on all my consuming pages.

   40     <%= Html.MySelector("selectedIDs", "selectedNames", "State")%>

Sunday, January 31, 2010 9:50:19 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   ASP.NET | ASP.NET MVC | C# | Javascript  | 
# Thursday, November 26, 2009

I’ve been talking about Geneva for a long time. I got the basics down earlier in the year. I tried to come up with my own set of sample apps, but failed to get anywhere. With the official release, and renaming to Windows Identity Foundation (WIF), I have renewed inspiration.

I read Michele Leroux Bustamante’s MSDN magazine article, Claim-Based Authorization with WIF, last night. After reading the article, I was confident that I could get a claims-aware WCF service stood up with a custom STS in a matter of hours. Today I downloaded and installed WIF. I also installed the WIF SDK and all of the prerequisite hotfixes. I perused the readme files and looked through some of the samples code. Everything is layed out sensibly, the samples are commented sufficiently, and the samples include setup and cleanup batch scripts when necessary.

The samples include:

Quick Start

  1. Simple Claims Aware Web Application
  2. Simple Claims Aware Web Service
  3. Simple Web Application With Information Card SignIn
  4. Simple Web Application With Managed STS
  5. Claims Aware Web Application in a Web Farm
  6. Using Claims In IsInRole

End-to-end Scenario

  1. Authentication Assurance
  2. Federation For Web Services
  3. Federation For Web Applications
  4. Identity Delegation
  5. Web Application With Multiple SignIn Methods
  6. Federation Metadata

Extensibility

  1. Claims Aware AJAX Application
  2. Convert Claims To NT Token
  3. Customizing Request Security Token
  4. Customizing Token
  5. WSTrustChannel
  6. Claims-based Authorization

All of the samples I’ve run through so far are great. The only thing that I’m not in love with is all the XML required to wire this stuff up. Maybe some Juval-style extensions would make it less painful.

One more thing… it looks like all of the XP users will finally have to upgrade. WIF only works with Vista, Win7, and Win2008. I heard that Win2003 compatibility will arrive in December.

Download Windows Identity Foundation

Download Windows Identity Foundation SDK

Thursday, November 26, 2009 11:44:24 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   .NET Framework | AJAX | ASP.NET | C# | WCF | WIF  | 
# Wednesday, October 28, 2009

Using the NetTcpBinding on a WCF service is secure by default. Unless you override the default settings, you will enjoy Transport Security using Windows authentication and the EncrpytAndSign protection level. When you create a new WCF service library, Visual Studio creates a config file with the following identity block:

   24           <identity>

   25             <dns value="localhost"/>

   26           </identity>

 

If you wipe this config file clean like me to write a much cleaner and shorter config file, this identity block is the first thing to go. Sadly, most people also add a binding configuration with <security mode=”None”/>. I have done this too in an Intranet environment. The samples and book examples out there don’t show how to write an actual production environment service that cares for different machines in the same domain. While the default settings work when testing on your local machine, they don’t work in a simple Intranet environment.

Most of the difficulty I experienced when starting to work with WCF was getting security to work with the TCP binding. Everything worked so easily during development, but everything broke down once deployed to the development server. It didn’t help that the only errors I saw were timeout exceptions. If I had known about the Service Trace Viewer, I could have easily determine the cause and Googled (Bing wasn’t around then) for a solution. Instead, I chose the easier (and much less secure) way out… rely on my firewall and turn security off.

As mentioned before, the NetTcpBinding is secure by default with transport security using Windows authentication. The problem most experience when moving the service to a different machine is caused by NT authentication failing. If you use svcutil to generate your client config file and your host doesn’t have the identity block mentioned above, svcutil will not add a key piece of information to the client config file. The missing element is, you guessed it, the identity block. Without it, you will likely get an exception and see a stack trace similar to this:

[System.ServiceModel.Security.SecurityNegotiationException: A call to SSPI failed, see inner exception.]
...
[System.Security.Authentication.AuthenticationException: A call to SSPI failed, see inner exception.]
...
[System.ComponentModel.Win32Exception: The target principal name is incorrect.]
...

If you add tracing to your client, you will see that without specifying an identity block WCF will make the call with a DNS identity set to the name of the host. Notice the blue arrows.

image

You can see that the EndpointReference does not have an <Identity> block. Without that identity block, WCF cannot create a valid ServicePrincipalName. You can find this in Reflector, following this path:

  • System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider+WindowsStreamSecurityUpgradeInitiator.OnInitiateUpgrade() – This is where the SecurityNegociationException is being thrown.
  • System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider+WindowsStreamSecurityUpgradeInitiator.InitiateUpgradePrepare() – This method populates an EndpointIdentity and ServicePrincipalName to be used immediately after for NT authentication.

image

When the identity is not specified, it falls back to trying to create an SPN from the host address. I have seen this work on a machine that has two DNS names, using the DNS name that does not match the NETBIOS or AD name for the machine. I’m not exactly sure why that works.

Having any of the following identity blocks in your client config file will cause WCF to take the first path that successfully creates an SPN needed to perform NT authentication in the AuthenticateAsClient method called from OnInitiateUpgrade():

  • <dns value=”serviceHostName”/>
  • <dns/>
  • <servicePrincipalName value=”domain\hostServiceUserAccount”/>
  • <servicePrincipalName/>

Having these <Identity> settings in your client config file adds the appropriate <Identity> settings in the <EndpointReference> used when opening the channel.

image

Security seems more mysterious when going rogue and writing your own config files. If you go rogue, make sure you use the appropriate <Identity> blocks. With this mystery solved, <security mode=”None”/> is a thing of the past. Now we can keep our services secure in an Intranet environment.

Wednesday, October 28, 2009 8:30:22 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   .NET Framework | C# | Dev Tools | WCF  | 
# Tuesday, October 13, 2009

Web services are just the tip of the iceberg in WCFI was privileged to attend the IDesign WCF Master Class last week. It only comes to the USA one time each year, and is presented by the one and only Juval Lowy. The class is held at the training center on the Microsoft Silicon Valley campus in Mountain View, CA. Five very intense days of WCF covering all aspects of WCF from essentials like the ABCs to the most intricate details about advanced topics like concurrency, security, transactions, and the service bus.

What we’ve been told sold about WCF from Microsoft is truly just the tip of the iceberg. Juval presents countless examples that prove WCF is not just about web services. WCF is the evolution of .NET, providing world-class features that no class should ever be without.

Demos, samples, and labs are presented using .NET 3.5 and 4.0 with an emphasis on the new features and functionality in 4.0. Discovery and announcements are the most underrated and unknown new features of WCF 4.0. After seeing Juval’s demos on discovery and announcement, I can’t imagine creating services without them.

More than all of the WCF content, the class gives you a lot to think about regarding architecture, the framework, and engineering principles. Juval’s mastery of .NET is evident in his ServiceModelEx library that extends almost all aspects of WCF and the service bus. His “one line of code” motto makes it possible for all of us to configure our WCF services with ease. The ServiceModelEx library is a good example for all developers to know and understand how to “do .NET” the right way. It exemplifies the best of what .NET and WCF have to offer.

Check out the IDesign website to get the WCF Resource CD (containing many of the examples and demos from the class). Also note the next class dates and sign up for the IDesign newsletter.

Tuesday, October 13, 2009 8:59:33 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   .NET Framework | C# | Cloud | Dev Tools | Futures | WCF  | 
# Wednesday, September 02, 2009
The 2009 Jacksonville Code Camp was a great success. Many thanks to Bayer, Brandy, and everyone else that made it happen. The bar has been set really high for future Jacksonville code camps, and for the rest of Florida too.

My session on Transactional WCF Services went well. Many great questions and compliments after the session. If you attended and have any unanswered questions, please email me.

You can download the session files below. It contains staged versions of all of the transaction modes we discussed. It also contains a tracing solution and tracing result files to view the client and host tracing files in Client/Service mode. Also see my previous post on using the Service Trace Viewer. It also contains a few demo projects that we didn't get to in the one-hour session.

Files/Solutions included in Session Archive:
  • PowerPoint slides
  • Transaction Promotion Code Snippet
  • Testing database backup
  • Testing SQL script (query and cleanup between tests)
  • IDesign ServiceModelEx Project (used by all included Solutions)
  • Code Demo Solutions

Code Demos include:

1. TransactionScope - Shows how single/multiple resource managers affect which Transaction Manager is chosen to handle the scoped transaction. Also gives first look at transaction promotion detection.
2a. Mode None - WCF transaction mode with which no transactions are created or flowed from the calling client.
2b. Mode Service - WCF transaction mode with which no transactions are flowed from the calling client, but a transaction is created for your service operation.
2c. Mode Client - WCF transaction mode with which a transaction is required to be flowed, and the service will only use the client transaction.
2d. Mode Client/Service - WCF transaction mode with which a client transaction will be flowed and used by the service, if available. If no client transaction is flowed, a transaction will be provided automatically for the service operation.
3. Explicit Voting - Shows how explicit voting with a session-mode service is performed using OperationContext.Current.SetTransactionComplete().
4a. Testing Various Resource Managers - Shows how a client can use a single TransactionScope to call several services (some transactional, some non-transactional), a database stored procedure, and an IDesign volatile resource manager Transactional<int>.
4b. Testing Services - Provides a host project for a transactional service and a non-transactional service used in 4a.
5a. Tracing - Same as 2d. modified with the additional app.config settings in the client and host projects to allow for service tracing to .svclog files.
5b. Tracing Results - Stored results from executing 5a. in case you don't want to load the database and actually run the projects. The .stvproj file can be opened directly in the Service Trace Viewer. On the "Activity" table, click on the activity "Process action 'http://services/gotjeep.net/GpsTrackServiceContract/SubmitTrack'" then click on the "Graph" tab. You will see that the client and host activities where the arrow moves from client to host (send and receive message, respectively) show the OleTxTransaction in "Headers." The next activity in the host reads "The transaction '5bd25b08-848c-409d-9163-6303b9138382:1' was flowed to operation 'SubmitTrack'."

 

Download the session files:
TransactionalWCF.zip (854 KB)

Wednesday, September 02, 2009 10:34:34 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]   .NET Framework | C# | Dev Community | Dev Tools | MSDTC | Transactions | WCF  | 
# Saturday, December 06, 2008

It's so easy! Start downloading Enterprise Library 4.1 now while you read this. The data application block syntax has not changed much since the first version. The most notable change was allowing us to use System.Data.Common.DbCommand when version 3.0 was released. I understand the uneasy feeling some developers have using Enterprise Library. My team at my previous employer decided not to use it, thinking it would add increased complexity and would not give us the flexibility we needed if we had to change something. This is typical of groups that do not have an established Data Access Library.

Your Data Access Library should be one of the most highly tested libraries in your application. If there is a problem there, you will have issues everywhere. Enterprise Library not only comes with the source code, but also includes the full suite of unit tests for each of the application blocks. You should feel at ease when you decide to migrate to Enterprise Library. Run it through your full battery of tests before you commit the team to it. If you find any problems, check the forums, request changes/enhancements from the MS Patterns & Practices team, or fix it yourself.

The steps to achieve EntLib goodness:

  1. Download Enterprise Library
  2. Add reference to "Enterprise Library Data Access Application Block" and "Enterprise Library Shared Library"
  3. Change your app.config or web.config
  4. Write some much more readable data access code

I'll start at step 3 as steps 1 and 2 are self-explanatory. Your connection string needs to be in you app's config file, the machine.config file, or in a connectionStrings.config file referenced in those config files. You can start using it just by adding the <configSections> clock and the <dataConfiguration> node. This will allow you to have one default database for all commands you will execute.

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

<configuration>

    <configSections>

        <section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

    </configSections>

    <dataConfiguration defaultDatabase="Testing" />

    <connectionStrings>

        <add name="Testing" connectionString="server=Server_Name;database=DB_Name;Integrated Security=true;"

                  providerName="System.Data.SqlClient" />

    </connectionStrings>
</configuration>

 

By the time you get to step 4, you have all of the infrastructure in place. Painless so far, let's see how steep the learning curve is.

With ADO.NET, you would write:

  116 string connectionString = ConfigurationManager.ConnectionStrings["Testing"].ConnectionString;

  117 using (SqlConnection con = new SqlConnection(connectionString))

  118 using (SqlCommand cmd = new SqlCommand("usp_ErrorLog_Insert", con))

  119 {

  120     cmd.CommandType = System.Data.CommandType.StoredProcedure;

  121     cmd.Parameters.AddWithValue("Message", "Testing 1");

  122     cmd.Parameters.AddWithValue("UserID", 5150);

  123     try

  124     {

  125         con.Open();

  126         cmd.ExecuteNonQuery();

  127     }

  128     finally

  129     {

  130         con.Close();

  131     }

  132 }

With Enterprise Library, you write:

  170 Database db = DatabaseFactory.CreateDatabase();

  171 DbCommand cmd = db.GetStoredProcCommand("usp_ErrorLog_Insert");

  172 db.AddInParameter(cmd, "Message", System.Data.DbType.String, "Testing 1");

  173 db.AddInParameter(cmd, "UserID", System.Data.DbType.Int32, 5150);

  174 db.ExecuteNonQuery(cmd);

 

Line 170 creates the database object. This is the hardest thing to get used to. You call everything related to the Database object. In ADO.NET, we are used to creating a connection, adding the connection to a command, using the command in an adapter. Here you'll always be using the Database object to create a command, add parameters to the command, execute the command, fill a DataSet, etc. It is definitely less code to write, but it is also more readable and elegant.

If you have a database to execute commands against other than the defaultDatabase specified in the config file, then the first line changes to:

  170 Database db = DatabaseFactory.CreateDatabase("OtherConnectionStringKey");

 

That's it. The patterns & practices team has really done a nice job making it painless to use Enterprise Library. Take the time to try it out again if you reviewed a previous version. I reviewed 2.0, and chose not to use it. When 3.0 came out, I was hooked.

Saturday, December 06, 2008 11:09:10 PM (Eastern Standard Time, UTC-05:00)  #    Comments [6]   C# | Enterprise Library  | 
# Thursday, December 04, 2008

Unless you are working on a extremely simple or read-only application, transactions are a must. Using the System.Transactions namespace is the easiest and most efficient way to maintain system consistency when dealing with multiple calls or multiple resources. Although System.Transactions arrived in .NET in the 2005 product, it is still a relatively unknown part of the framework. System.Transactions was designed by the Indigo team in preparation for WCF. It is not compatible with Win98 or WinME, but most people are incompatible with Win98 and WinME so it works out just fine.

Before System.Transactions, we only had access to System.Data.SqlClient.SqlTransaction or a true SQL transaction using BEGIN/ROLLBACK/COMMIT TRAN. Using SQL transactions, you are stuck with only being able to update DB records as part of your transaction. If you wanted to change a cached value in your app in addition to the SQL updates in the same transaction then you would be out of luck. This also required a lot of transaction code in your stored procedures, writing stored procedures that can be called independently or part of transaction made for very messy stored procedures and often led to multiple stored procedures that served the same purpose.

Using the SqlTransaction class was also messy. The most important restriction is that you need to have all database calls inside the same SqlConnection. This does not work well for a well-designed N-tier application. The other problem is that you need to handle your own non-DB transaction logic inside the same SqlTransaction and conditionally commit/rollback as necessary. This all tends to lead to several try-catch statements within one SqlTransaction. Handling the plumbing to manually add each SqlCommand to the transaction gets old quickly too.

Using SqlTransaction

   27 string connectionString = ConfigurationManager.ConnectionStrings["Testing"].ConnectionString;

   28 using (SqlConnection con = new SqlConnection(connectionString))

   29 {

   30     SqlTransaction tran = null;

   31     try

   32     {

   33         con.Open();

   34         tran = con.BeginTransaction();

   35         using (SqlCommand cmd = new SqlCommand("usp_ErrorLog_Insert", con))

   36         {

   37             cmd.Transaction = tran;

   38             cmd.CommandType = System.Data.CommandType.StoredProcedure;

   39             cmd.Parameters.AddWithValue("Message", "Testing 1");

   40             cmd.Parameters.AddWithValue("UserID", 5150);

   41             cmd.ExecuteNonQuery();

   42         }

   43 

   44         using (SqlCommand cmd = new SqlCommand("usp_ErrorLog_Insert", con))

   45         {

   46             cmd.Transaction = tran;

   47             cmd.CommandType = System.Data.CommandType.StoredProcedure;

   48             cmd.Parameters.AddWithValue("Message", "Testing 2");

   49             cmd.Parameters.AddWithValue("UserID", 5150);

   50             cmd.ExecuteNonQuery();

   51         }

   52 

   53         tran.Commit();

   54     }

   55     catch

   56     {

   57         if (tran != null) tran.Rollback();

   58     }

   59     finally

   60     {

   61         con.Close();

   62     }

   63 }

 

System.Transactions liberated us from the mundane SqlClient code and repetitive try-catches. simply wrapping your old ADO.NET with a using (TransactionScope) { } is all you need to do. You will typically add a transactionScope.Complete() statement as the last line in the TransactionScope using block is really all you need. Any exception thrown before this point will break out of scope, implicitly aborting the transation. Much better.

System.Transactions uses the LTM (Lightweight Transaction Manager) when dealing with single resources or machines. The transaction is automatically promoted to MSDTC (Microsoft Distributed Transaction Coordinator) when another resource is enlisted in a transaction. A lot of people struggle with MSDTC because it is difficult to setup, requires special firewall considerations, and doesn't really work well for smart client applications since you have to install MSDTC on every client machine.

I'll show one transaction performed three different ways and show what happens with the LTM and MSDTC for each of them. I will also demonstrate an excellent reason to migrate to Enterprise Library.

1) Executing two ADO.NET SqlCommands in different SqlConnections

  122 using (TransactionScope scope = new TransactionScope())

  123 {

  124     string connectionString = ConfigurationManager.ConnectionStrings["Testing"].ConnectionString;

  125     using (SqlConnection con = new SqlConnection(connectionString))

  126     using (SqlCommand cmd = new SqlCommand("usp_ErrorLog_Insert", con))

  127     {

  128         cmd.CommandType = System.Data.CommandType.StoredProcedure;

  129         cmd.Parameters.AddWithValue("Message", "Testing 1");

  130         cmd.Parameters.AddWithValue("UserID", 5150);

  131         try

  132         {

  133             con.Open();

  134             cmd.ExecuteNonQuery();

  135         }

  136         finally

  137         {

  138             con.Close();

  139         }

  140     }

  141 

  142     Console.WriteLine("Local Transaction ID: {0}",

  143         Transaction.Current.TransactionInformation.LocalIdentifier);

  144     Console.WriteLine("Distributed Transaction ID: {0}",

  145         Transaction.Current.TransactionInformation.DistributedIdentifier.ToString());

  146 

  147     using (SqlConnection con = new SqlConnection(connectionString))

  148     using (SqlCommand cmd = new SqlCommand("usp_ErrorLog_Insert", con))

  149     {

  150         cmd.CommandType = System.Data.CommandType.StoredProcedure;

  151         cmd.Parameters.AddWithValue("Message", "Testing 2");

  152         cmd.Parameters.AddWithValue("UserID", 5150);

  153         try

  154         {

  155             con.Open();

  156             cmd.ExecuteNonQuery();

  157         }

  158         finally

  159         {

  160             con.Close();

  161         }

  162     }

  163 

  164     Console.WriteLine("Local Transaction ID: {0}",

  165         Transaction.Current.TransactionInformation.LocalIdentifier);

  166     Console.WriteLine("Distributed Transaction ID: {0}",

  167         Transaction.Current.TransactionInformation.DistributedIdentifier.ToString());

  168 

  169     scope.Complete();

  170 }

 

This writes the following to the command line:

Local Transaction ID: e90f47f4-df80-496b-a9c0-0c45b2f452c4:2
Distributed Transaction ID: 00000000-0000-0000-0000-000000000000
Local Transaction ID: e90f47f4-df80-496b-a9c0-0c45b2f452c4:2
Distributed Transaction ID: 1fad8108-ddae-496a-a7da-ce92df175e40

You'll notice that the first command creates a transaction using LTM as indicated by the Local Transaction ID. After the second command is executed, the transaction is promoted to DTC as indicated by the Distributed Transaction ID. This is expected because there are two distinct SqlConnections. Even though the connection string is the same, TransactionScope treats these ADO.NET objects as unique resources.

This has additional implications when connection pooling comes into play. After I close the first connection, it is returned to the pool and is available for use. If this connection is requested for use, it will no longer be available to commit or abort this transaction, and you will see the dreaded MSDTC error "Communication with the underlying transaction manager has failed."

2) Executing two ADO.NET SqlCommands in the same SqlConnection

   69 string connectionString = ConfigurationManager.ConnectionStrings["Testing"].ConnectionString;

   70 using (TransactionScope scope = new TransactionScope())

   71 using (SqlConnection con = new SqlConnection(connectionString))

   72 {

   73     using (SqlCommand cmd = new SqlCommand("usp_ErrorLog_Insert", con))

   74     {

   75         cmd.CommandType = System.Data.CommandType.StoredProcedure;

   76         cmd.Parameters.AddWithValue("Message", "Testing 1");

   77         cmd.Parameters.AddWithValue("UserID", 5150);

   78         try

   79         {

   80             con.Open();

   81             cmd.ExecuteNonQuery();

   82         }

   83         finally

   84         {

   85             con.Close();

   86         }

   87     }

   88 

   89     Console.WriteLine("Local Transaction ID: {0}",

   90         Transaction.Current.TransactionInformation.LocalIdentifier);

   91     Console.WriteLine("Distributed Transaction ID: {0}",

   92         Transaction.Current.TransactionInformation.DistributedIdentifier.ToString());

   93 

   94     using (SqlCommand cmd = new SqlCommand("usp_ErrorLog_Insert", con))

   95     {

   96         cmd.CommandType = System.Data.CommandType.StoredProcedure;

   97         cmd.Parameters.AddWithValue("Message", "Testing 2");

   98         cmd.Parameters.AddWithValue("UserID", 5150);

   99         try

  100         {

  101             con.Open();

  102             cmd.ExecuteNonQuery();

  103         }

  104         finally

  105         {

  106             con.Close();

  107         }

  108     }

  109 

  110     Console.WriteLine("Local Transaction ID: {0}",

  111         Transaction.Current.TransactionInformation.LocalIdentifier);

  112     Console.WriteLine("Distributed Transaction ID: {0}",

  113         Transaction.Current.TransactionInformation.DistributedIdentifier.ToString());

  114 

  115     scope.Complete();

  116 }

 

This writes the following to the command line:

Local Transaction ID: e90f47f4-df80-496b-a9c0-0c45b2f452c4:1
Distributed Transaction ID: 00000000-0000-0000-0000-000000000000
Local Transaction ID: e90f47f4-df80-496b-a9c0-0c45b2f452c4:1
Distributed Transaction ID: becac9c9-e15f-4370-9f73-7f369665bed7

This is not expected because both commands are part of the same connection. Of course I am closing the connection to simulate an N-tier app where the data access layer is maintaining it's own SQL access, opening and closing its connection as it should. If I did not close the connection, you would not see a Distributed Transaction ID after the second command.

3) Executing two Enterprise Library commands

  176 using (TransactionScope scope = new TransactionScope())

  177 {

  178     Database db = DatabaseFactory.CreateDatabase("Testing");

  179     DbCommand cmd = db.GetStoredProcCommand("usp_ErrorLog_Insert");

  180     db.AddInParameter(cmd, "Message", System.Data.DbType.String, "Testing 1");

  181     db.AddInParameter(cmd, "UserID", System.Data.DbType.Int32, 5150);

  182     db.ExecuteNonQuery(cmd);

  183 

  184     Console.WriteLine("Local Transaction ID: {0}",

  185         Transaction.Current.TransactionInformation.LocalIdentifier);

  186     Console.WriteLine("Distributed Transaction ID: {0}",

  187         Transaction.Current.TransactionInformation.DistributedIdentifier.ToString());

  188 

  189     Database db1 = DatabaseFactory.CreateDatabase("Testing1");

  190     DbCommand cmd1 = db.GetStoredProcCommand("usp_ErrorLog_Insert");

  191     db1.AddInParameter(cmd1, "Message", System.Data.DbType.String, "Testing 2");

  192     db1.AddInParameter(cmd1, "UserID", System.Data.DbType.Int32, 5150);

  193     db1.ExecuteNonQuery(cmd1);

  194 

  195     Console.WriteLine("Local Transaction ID: {0}",

  196         Transaction.Current.TransactionInformation.LocalIdentifier);

  197     Console.WriteLine("Distributed Transaction ID: {0}",

  198         Transaction.Current.TransactionInformation.DistributedIdentifier.ToString());

  199 

  200     scope.Complete();

  201 }

 

This writes the following to the command line:

Local Transaction ID: 6737b756-2d5b-4eff-902d-15f9ccd5c26f:3
Distributed Transaction ID: 00000000-0000-0000-0000-000000000000
Local Transaction ID: 6737b756-2d5b-4eff-902d-15f9ccd5c26f:3
Distributed Transaction ID: 00000000-0000-0000-0000-000000000000

Whoa! How cool is that? No DTC promotion. Enterprise Library is intelligently deciding to keep the connection open when it is part of the transaction. This will save a lot of wasted time as the promotion to DTC adds a noticeable delay. If I wasn't using Enterprise Library already, I'd switch now.

Useful links:

Thursday, December 04, 2008 9:50:25 PM (Eastern Standard Time, UTC-05:00)  #    Comments [8]   C# | MSDTC | SQL | Transactions  | 
# Wednesday, October 29, 2008

Don't be so quick to blame the service or MSDTC when you see the error "Communication with the underlying transaction manager has failed."

Symptom:

An error message that reads something like:

System.Transactions.TransactionManagerCommunicationException: Communication with the underlying transaction manager has failed. ---> System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component.

Solutions:

 

"Check your firewall settings" is what you will find in almost all forum posts and on MSDN. You need port 135 open bi-directionally for RPC's end point mapper (EPM). You also need ports 1024-5000 open bi-directionally if you have not specified your own port settings for RPC in the registry. If you have your own ports specified in the registry, then those need to be open bi-directionally.

 

WHAT ?!? It may also be your code causing the issue. If you are using TransactionScope, you have to be mindful of every method called within the using braces. Looking at the code below, you will see two service calls and a seemingly innocuous ShouldContinue() method checking to see if the second operation should be called.

using (TransactionScope scope = new TransactionScope())

using (MyServiceClient proxy = new MyServiceClient())

{

       proxy.DoOperationOne(someID);

      

       if (ShouldContinue()) // uh, oh! What if this has an ADO.NET connection that is opened and closed inside it?

       {

               proxy.DoOperationTwo(someOtherID);

       }

}

If ShouldContinue() opens and closes an SqlConnection, the TransactionScope object has no means by which to commit or rollback this part of the transaction. This will cause the error "Communication with the underlying transaction manager has failed."

 

1. If you do not need to results of DoOperationOne() to feed ShouldContinue(), then do that logic before the TransactionScope using block.

2. If you do need the result of DoOperationOne() to feed ShouldContinue(), then you can wrap the internals of ShouldContinue() with a TransactionScope using block specifying TransactionScopeOption.Suppress. This will not add the resource access contained within the block to the ambient transaction.

3. Use an intelligent data access library like Enterprise Library that manages your connections for you. It won't close the connection if enlisted in a transaction.

 

Look at your code before you involve your network dudes. This is more common when integrating legacy code with new service calls.

Wednesday, October 29, 2008 9:54:29 PM (Eastern Standard Time, UTC-05:00)  #    Comments [9]   .NET Framework | C# | MSDTC | WCF  | 
# Sunday, September 21, 2008

WCF never ceases to amaze me. Around every corner is another fascinating use for WCF, and much forethought on Microsoft's part to make it look and behave great. I wanted to expose my services to my AJAX functions on my web site. I did not want to change my class library because it is used by other clients. I could just add the service classes to this web site, but why re-do when you can re-use.

If you have an existing WCF Service Library, you will need to expose it with the AspNetCompatibilityRequirementsMode.Allowed attribute on the service class to make it visible to ASP.NET clients. To avoid changing your service library in any way, the easiest thing to do is to add a new class to your web site that inherits from your service class. In this example, my existing service library uses the JeepServices namespace. Notice there is no implementation in this class. It is simply a placeholder for the real service implementation with the compatibility attribute attached.

    1 using System.ServiceModel;

    2 using System.ServiceModel.Activation;

    3 

    4 [ServiceBehavior]

    5 [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

    6 public class WebHttpService : JeepServices.Service

    7 {

    8 }

Now that I have a ASP.NET compatible service, I need to expose it to the web site clients. Create a service file (.svc), and change the Service and CodeBehind attributes to point to the .svc file. The last thing you need is the Factory attribute. This notifies WCF of this service, eliminating the need for a configuration file entry for the service endpoint. In fact, you don't even need the <system.servicemodel> in your configuration file at all. This is because it is only hosted as a web script, and cannot be called outside of the web site.

    1 <%@ ServiceHost Language="C#" Debug="true" Service="WebHttpService" CodeBehind="~/App_Code/WebHttpService.cs"

    2     Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" %>

 

In your web page you will need a few things. First your will need a ScriptManager with a ServiceReference to the .svc file. You will then need the Javascript functions to make the call (DoJeepWork), handle the success message (OnJeepWorkSucceeded), and handle the failure message (OnJeepWorkFailed). Notice in DoJeepWork that you don't call the service by it's service name WebHttpService, you call it by the ServiceContract namespace and name. For this example, my interface has ServiceContract attributes Namespace = "JeepServices", and Name = "JeepServiceContract". Now you just wire up a ASP.NET control's OnClientClick or an input or anchor tag's onclick to DoJeepWork() and you are good to go.

    1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

    2 

    3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

    4 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    5 <html xmlns="http://www.w3.org/1999/xhtml">

    6 <head runat="server">

    7     <title>Test page</title>

    8 

    9     <script type="text/javascript">

   10         function DoJeepWork() {

   11             JeepServices.JeepServiceContract.DoWork(OnJeepWorkSuccedeed, OnJeepWorkFailed);

   12         }

   13         function OnJeepWorkSuccedeed(res) {

   14             document.getElementById("<%= this.lblMessage.ClientID %>").innerText = res;

   15         }

   16         function OnJeepWorkFailed(error) {

   17             // Alert user to the error.   

   18             alert(error.get_message());

   19         }

   20     </script>

   21 

   22 </head>

   23 <body>

   24     <form id="form1" runat="server">

   25     <div>

   26         <asp:ScriptManager runat="server">

   27             <Services>

   28                 <asp:ServiceReference Path="~/Services/WebHttpService.svc" InlineScript="false" />

   29             </Services>

   30         </asp:ScriptManager>

   31         <asp:Label ID="lblMessage" runat="server" Text="No work has been done" />

   32         <a href="javascript:void(0); DoJeepWork()">Do Work</a>

   33     </div>

   34     </form>

   35 </body>

   36 </html>

 

Mission accomplished! Here you've seen how to expose an existing WCF service library without changing any code in the library itself. Adding two files allowed the service to be exposed to your AJAX clients. Best of all, there is no configuration file changes to make.

Useful Links:

Sunday, September 21, 2008 11:21:24 AM (Eastern Standard Time, UTC-05:00)  #    Comments [9]   .NET Framework | AJAX | ASP.NET | C# | Javascript | WCF  | 
# Tuesday, September 16, 2008

Hosting an MSMQ service is a little bit different than the other bindings. Since WCF is using MSMQ as a transport mechanism, you must setup the queues, permissions, and bindingConfigurations to allow this to happen. Surprisingly, MSDN has a good sample article that goes into sufficient detail on how to set this up for the 3.5 WF-WCF-CardSpace samples.

I have read in other articles that the AppPool must have an interactive identity and that the queue names needed to match the name of the .svc file. I did not find this to be the case. I was able to use the NetworkService account for my AppPool after adding receive and peek permissions for NetworkService on my queue. Communication between client and WAS worked fine with my service file named WasServices.svc and my queue address as net.msmq://localhost/private/QueuedService1.

You can download my solution with the following link: WasServices.zip (78K)

Additional Info:

Tuesday, September 16, 2008 8:48:22 PM (Eastern Standard Time, UTC-05:00)  #    Comments [5]   .NET Framework | C# | WAS | WCF  | 
# Saturday, September 13, 2008

It has taken me weeks to get WAS (Windows Activation Service) working. Finally, tonight, my long hours of research has paid off. After everything I tried, it turned out to be a general IIS7 issue caused by a stray http reservation that I probably entered months ago during some testing. As I primarily use the built-in development server for web development, I rarely crank up an IIS site on my development machine.

This post by Phil Haack helped me fix my IIS install:

http://haacked.com/archive/2007/05/21/the-iis-7-team-rocks.aspx

I have been cursing IIS7, Vista, and WAS for weeks. I should have been cursing my own lack of IIS7 knowledge all along. Now that it's working, I am a big fan of WAS. From the tone of recent forum responses and blog posts, very few people are using WAS. Maybe it is due to Windows Server 2008 being so new. Not many people have Vista workstations for development and all Windows Server 2008 servers to deploy to. Knowing how many problems I had, I can only assume others are experiencing the same thing. The only real info available right now is pre-release articles and MVP posts about the new features with a sneak peak example on how to get it to work. Even MSDN doesn't show how to use an existing WCF Service Library with WAS. They just walk through a WsHttpBinding example as a new WCF web site served up by WAS.

I'm posting the details so others will maybe see that it's really not that hard. For this example I want to expose this service with the NetTcpBinding to prove that it is not IIS hosting the service. I used the WCF Service Library project template for my WCF service, and named the project WasServices. So the lame Service1 service is all I have in the library. I made no changes to the project and built it in release mode to get the DLL. Some posts and articles out there say that the only way to get WAS to work is to have an HTTP-based WCF web site. This is simply not true. You just need to have an application set up in IIS.

Here is the steps to success:

1. Enable the required Windows Features to wake up IIS7 and WAS. You will find these in the helpful links below.

2. Configuration file C:\Windows\System32\inetsrv\config\applicationHost.config must be modified to enable the required protocols on your web site and application. You can modify the file yourself, or use command-line utilities.

To enable net.tcp on the web site, if it is not already:

%windir%\system32\inetsrv\appcmd.exe set site "Default Web Site" -+bindings.[protocol='net.tcp',bindingInformation='808:*']

To enable net.tcp on your application (my app is named WasServices) within that web site, if it is not already:

%windir%\system32\inetsrv\appcmd.exe set app "Default Web Site/WasServices" /enabledProtocols:http,net.tcp

Here is an exerpt from the applicationHost.config file showing the site and application settings:

  151             <site name="Default Web Site" id="1" serverAutoStart="true">

  152                 <application path="/">

  153                     <virtualDirectory path="/" physicalPath="%SystemDrive%\inetpub\wwwroot" />

  154                 </application>

  155                 <application path="/WasServices" applicationPool="WasHosting" enabledProtocols="http,net.tcp">

  156                     <virtualDirectory path="/" physicalPath="C:\inetpub\wwwroot\WasServices" />

  157                 </application>

  158                 <bindings>

  159                     <binding protocol="net.tcp" bindingInformation="808:*" />

  160                     <binding protocol="net.pipe" bindingInformation="*" />

  161                     <binding protocol="net.msmq" bindingInformation="localhost" />

  162                     <binding protocol="msmq.formatname" bindingInformation="localhost" />

  163                     <binding protocol="http" bindingInformation="*:80:" />

  164                 </bindings>

  165             </site>

3. Prepare the application in your application folder (C:\inetpub\wwwroot\WasServices)

Create a service file (WasServices.svc) that points to your existing WCF service library:

    1 <%@ ServiceHost Service="WasServices.Service1" %>

 

Create a web.config file that specifies the service's endpoints:

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

    2 <configuration>

    3     <system.serviceModel>

    4         <services>

    5             <service name="WasServices.Service1"

    6                     behaviorConfiguration="MEX">

    7                 <endpoint address="wsHttp"

    8                           binding="wsHttpBinding"

    9                           contract="WasServices.IService1"/>

   10                 <endpoint address="netTcp"

   11                           binding="netTcpBinding"

   12                           bindingConfiguration="NetTcpBinding_Common"

   13                           contract="WasServices.IService1"/>

   14                 <endpoint address="mex"

   15                           binding="mexHttpBinding"

   16                           contract="IMetadataExchange" />

   17             </service>

   18         </services>

   19         <behaviors>

   20             <serviceBehaviors>

   21                 <behavior name="MEX">

   22                     <serviceMetadata httpGetEnabled="true"/>

   23                 </behavior>

   24             </serviceBehaviors>

   25         </behaviors>

   26         <bindings>

   27             <netTcpBinding>

   28                 <binding name="NetTcpBinding_Common">

   29                     <reliableSession enabled="true"/>

   30                     <security mode="None"/>

   31                 </binding>

   32             </netTcpBinding>

   33         </bindings>

   34     </system.serviceModel>

   35 </configuration>

 

Place the release-compiled DLL created from the WCF Service Library in a new folder named Bin.

4. At this point, you can browse and see the familiar "You have created a service." page for Service1.

5. Write your proxy file and config file.

WAS and IIS7 decide the address for your service, and it is not intuitive.

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

    2 <configuration>

    3     <system.serviceModel>

    4         <client>

    5             <endpoint address="net.tcp://localhost/WasServices/WasServices.svc/netTcp"

    6                       binding="netTcpBinding"

    7                       bindingConfiguration="NetTcpBinding_IService1"

    8                       contract="WasServices.IService1"

    9                       name="NetTcpBinding_Common" />

   10         </client>

   11         <bindings>

   12             <netTcpBinding>

   13                 <binding name="NetTcpBinding_Common">

   14                     <reliableSession enabled="true" />

   15                     <security mode="None" />

   16                 </binding>

   17             </netTcpBinding>

   18         </bindings>

   19     </system.serviceModel>

   20 </configuration>

 

The /netTcp at the end of the address is due to the address specified in the service's web.config file. The address was given there as simply netTcp. This is because IIS7 and WAS decide your address based on the available bindings and ports you specified in the applicationHost.config file using appcmd.exe. Since my enabled protocols are http and net.tcp and the only open tcp port is 808, you will not see a port number in the address. The same would go for my wsHttpBinding since the only allowable port is 80.

I'm proud to be the fourth, and maybe final, member of the "Got WAS to work" club. If anyone wants to join, and needs help to get in... please let me know.

Here are some helpful links for those of you having problems:

Friday, September 12, 2008 11:58:31 PM (Eastern Standard Time, UTC-05:00)  #    Comments [8]   .NET Framework | C# | WAS | WCF  | 
# 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 [4]   .NET Framework | C# | LINQ | WCF  | 
# Tuesday, August 26, 2008

I decided to come out of my cave and look around 3.5 a bit. I haven't read much about extension methods, but find them quite useful. They are nothing more than a syntactically superior static helper method. Let's look at a quick example so I can get back to coming up with more excuses to use them everywhere.

I like to batch my database calls as much as possible to avoid repeated opening/closing of connections, etc. To do this, I pass a bunch of ID values into a stored procedure as a comma-separated string. In the stored proc, I break the string apart with everyone's favorite table-valued function fn_MakeTable() to make a table of IDs. Then I can JOIN, UPDATE, or INSERT as needed.

So let's say I have a collection of Orders which I can easily convert to an array of OrderID integers with LINQ. My new best friend to create a comma-separated string of OrderIDs is the following.

    1 using System;

    2 using System.Configuration;

    3 

    4 namespace Common

    5 {

    6     public static class ArrayHelper

    7     {

    8         public static string ToCsv<T>(this T[] array)

    9         {

   10             Converter<T, string> converter = (t) =>

   11                 {

   12                     return t.ToString();

   13                 };

   14             return ToCsv(array, converter);

   15         }

   16 

   17         public static string ToCsv<T>(this T[] array, Converter<T, string> converter)

   18         {

   19             CommaDelimitedStringCollection csv = new CommaDelimitedStringCollection();

   20             foreach (T t in array)

   21             {

   22                 csv.Add(converter(t));

   23             }

   24             return csv.ToString();

   25         }

   26     }

   27 }

 

You'll see that I have two ToCsv() methods. The first takes a generic array using the this keyword and uses .ToString() as a default converter to string. The second method requires you to additionally pass in a converter to convert the object of type T to a string. Take those converted strings, add them to a CommaDelimitedStringCollection and .ToString() that collection to a full CSV string of integer values.

There are two ways to call these extension methods. The first is the more familiar way. Since they are really nothing more than static helper methods, call them just like any other:

   14             int[] array = { 123, 456 };

   15             string csv = Common.ArrayHelper.ToCsv(array);

 

The second is the more elegant and more intuitive way. Call it as if it was built into the Framework:

   14             int[] array = { 123, 456 };

   15             string csv = array.ToCsv();

 

You may be wondering, what if I write a method that matches the signature of a built-in method like .ToString(). Well, the built-in methods take precedence over extension methods, so array.ToString() will still appear as System.Int32[]. To get your new meaning of .ToString(), you just have to call it in the static helper method way detailed above.

For a generic array of T, you will likely want to provide your own Converter if T's .ToString() method does not display the information you want to show in the CSV string. Below is a lame example of a converter. It takes the int value, converts it to the char value.

   21             Converter<int, string> converter = (i) =>

   22             {

   23                 return ((char)i).ToString();

   24             };

   25             string csv = array.ToCsv(converter);

 

I think something so simple, and definitely re-usable, would benefit any developer.

Tuesday, August 26, 2008 9:07:12 PM (Eastern Standard Time, UTC-05:00)  #    Comments [13]   .NET Framework | C# | LINQ  | 
# 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 [8]   .NET Framework | C# | WCF  | 
# Sunday, June 29, 2008

Enterprise applications store their data in a relational database. Our code reads the data stored in tables with many complex joins and business rule laden queries. We take the results of those queries and construct an equally complex business entity that is used by our application logic. Most developers, myself excluded, hate working with the database. Writing, modifying, or even seeing T-SQL causes some developers to itch. LINQ to SQL serves as a partially effective Hydrocortisone to relieve the itch. But they still need to maintain the schema, write SQL-mindful LINQ queries, and deal with the constant DataContext updates.

 

Imagine a world where you no longer need to translate your complex business entities to and from relational tables.  A world where there is no database backing store. A world where we create our business entities and store them in memory. Even better, in memory on a shared resource. Does it sound like an inconceivable futuristic developer heaven? Well it probably is, but this is really cool stuff in the works.

 

Enter the Microsoft project code-named "Velocity." The blurb on the overview page reads:

"Velocity" is a distributed in-memory application cache platform for developing scalable, high-performance applications. "Velocity" can be used to cache any CLR object and provides access through simple APIs. The primary goals for "Velocity" are performance, scalability and availability.

I have been working with the Digipede Network, the leading grid computing software solution, for a few months. The Velocity architecture sounds remarkably similar to Digipede's. I have seen the great benefits of the Digipede Network and have high expectations for Velocity.

The Digipede Network, for those of you that haven't seen it yet, consists of a central Digipede Server and one or many Digipede Agents. The server receives client requests and assigns tasks to the agents. The client uses the Digipede API to communicate with the server. The API pretty much wraps client-to-server and server-to-client WSE2 web service calls. This architecture allows you to take almost any CPU-intensive process and spread the workload among tens or hundreds of commodity or server grade machines. The result is a very high performing and easily scaled system with few code changes from what you do today.

Digipede Network Diagram:

Digipede Network Diagram

Digipede only works in this configuration, while Velocity has two proposed deployment models. You can have a "caching tier", similar to Digipede's Server and Agent configuration, or you can house Velocity as a Caching Service directly in IIS7. I don't know how communications will be handled between the client API and the "caching tier", but I assume it will be some sort of service calls (WCF perhaps). All CLR objects stored in the Velocity cache must be marked [Serializable] just as task worker classes must be to work with Digipede.

The Velocity API looks simple enough too. It exposes intuitive Get() and Put() methods where you call the cache by name. I can see how versioning of the cached objects might get tricky. Your application will also need a new configSection that specifies the deployment mode, locality, and also contains the list of cache hosts. As this is a distributed solution, the standard virtual machine playground doesn't work too well to really test this out.

This looks promising, and I'll be following the progress of the project closely.

Download Velocity

Download the Velocity CPT 1 samples

Saturday, June 28, 2008 11:11:00 PM (Eastern Standard Time, UTC-05:00)  #    Comments [7]   .NET Framework | C# | Database  | 
# 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 [5]   .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 [3]   .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 [9]   .NET Framework | WCF | C#  | 
# 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)]

   20     [return: System.ServiceModel.MessageParameterAttribute(Name="return")]

   21     decimal CalculateApproachAngle(decimal height, decimal diameter, decimal distance);

   22 }

   23 

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

   25 public interface GotJeepApproachAngleCalculatorPortTypeChannel : GotJeepApproachAngleCalculatorPortType, System.ServiceModel.IClientChannel

   26 {

   27 }

   28 

   29 [System.Diagnostics.DebuggerStepThroughAttribute()]

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

   31 public partial class GotJeepApproachAngleCalculatorPortTypeClient : System.ServiceModel.ClientBase<GotJeepApproachAngleCalculatorPortType>, GotJeepApproachAngleCalculatorPortType

   32 {

   33 

   34     public GotJeepApproachAngleCalculatorPortTypeClient()

   35     {

   36     }

   37 

   38     public GotJeepApproachAngleCalculatorPortTypeClient(string endpointConfigurationName) :

   39             base(endpointConfigurationName)

   40     {

   41     }

   42 

   43     public GotJeepApproachAngleCalculatorPortTypeClient(string endpointConfigurationName, string remoteAddress) :

   44             base(endpointConfigurationName, remoteAddress)

   45     {

   46     }

   47 

   48     public GotJeepApproachAngleCalculatorPortTypeClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :

   49             base(endpointConfigurationName, remoteAddress)

   50     {

   51     }

   52 

   53     public GotJeepApproachAngleCalculatorPortTypeClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :

   54             base(binding, remoteAddress)

   55     {

   56     }

   57 

   58     public decimal CalculateApproachAngle(decimal height, decimal diameter, decimal distance)

   59     {

   60         return base.Channel.CalculateApproachAngle(height, diameter, distance);

   61     }

   62 }

 

Add client endpoint to your config file:

You have the option of letting svcutil create your config file or using "Generate Service Reference", but I prefer to avoid the extra 20+ lines of config file defaults included by svcutil. It's not hard to write it yourself, and it ensures a much more readable config file.

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

    2 <configuration>

    3     <system.serviceModel>

    4         <bindings>

    5             <basicHttpBinding>

    6               <binding name="GotJeepApproachAngleCalculatorBinding">

    7                 <security mode="None"/>

    8               </binding>

    9             </basicHttpBinding>

   10         </bindings>

   11         <client>

   12             <endpoint address="http://www.gotjeep.net/services/approachAngleService.php"

   13                 binding="basicHttpBinding"

   14                 bindingConfiguration="GotJeepApproachAngleCalculatorBinding"

   15                 contract="GotJeepApproachAngleCalculatorPortType"

   16                 name="GotJeepApproachAngleCalculatorPort" />

   17         </client>

   18     </system.serviceModel>

   19 </configuration>

 

Write the client code:

    1 using System;

    2 

    3 namespace Client

    4 {

    5     class Program

    6     {

    7         static void Main(string[] args)

    8         {

    9             using (GotJeepApproachAngleCalculatorPortTypeClient proxy =

   10                 new GotJeepApproachAngleCalculatorPortTypeClient())

   11             {

   12                 decimal result = proxy.CalculateApproachAngle(25.6m, 33.2m, 17.75m);

   13                 Console.WriteLine("decimal result = proxy.CalculateApproachAngle(25.6m, 33.2m, 17.75m);");

   14                 Console.WriteLine("result = " + result.ToString("0.00"));

   15             }

   16         }

   17     }

   18 }

 

Check it out:

A request-reply call between WCF and a Nusoap PHP web service.

Saturday, March 22, 2008 9:25:00 PM (Eastern Standard Time, UTC-05:00)  #    Comments [5]   .NET Framework | C# | WCF  | 
# Sunday, January 20, 2008

In a previous post about the AJAX Extensions, I detailed the copy commands to retrieve the DLLs from the GAC. Same thing, this time for the ASP.NET 3.5 Extensions. If you are demoing CTP material in a hosted environment, you will likely need these in your app's bin to avoid the inevitable configuration error.

copy "C:\WINDOWS\assembly\GAC_MSIL\System.Web.Extensions\3.6.0.0__31bf3856ad364e35" C:\dev\MMVCApp\bin
copy "C:\WINDOWS\assembly\GAC_MSIL\System.Web.Extensions.Design\3.6.0.0__31bf3856ad364e35" C:\dev\MMVCApp\bin

Sunday, January 20, 2008 10:25:40 PM (Eastern Standard Time, UTC-05:00)  #    Comments [6]   .NET Framework | ASP.NET | ASP.NET MVC | C#  | 
# Tuesday, December 18, 2007

A great series of blog posts by Scott Guthrie about the ASP.NET MVC Framework coming soon as part of the ASP.NET 3.5 Extensions release.

Upon hearing the news, a few friends started questioning its intent, usefulness, and longevity. Many of us have been using or contemplating conversion to the MVP pattern, most recently using WCSF. The recent split of the MVP pattern by Fowler has caused many believers to question their faith. While many are still "proving" MVP, MVC has been around for nearly 30 years. Some believe that MVP and MVC can co-exist. Here is a comparison of MVP and MVC that concludes by painting an optimistic picture of MVP and MVC contributing to each other.

ASP.NET MVC appears to be the answer to my unit testing, REST, and code separation prayers. Thank you ScottGu and team!

Check it out!

Tuesday, December 18, 2007 9:06:20 PM (Eastern Standard Time, UTC-05:00)  #    Comments [3]   .NET Framework | AJAX | ASP.NET | ASP.NET MVC | C# | Javascript  | 
# Monday, December 17, 2007

Here is a fantastic solution to a common ORMr problem seen when regenerating code that overwrites changes made to previously generated and more recently manually-modified code.

Monday, December 17, 2007 10:04:16 PM (Eastern Standard Time, UTC-05:00)  #    Comments [6]   .NET Framework | C# | Database  | 
# Wednesday, November 21, 2007
Wednesday, November 21, 2007 10:08:56 PM (Eastern Standard Time, UTC-05:00)  #    Comments [8]   .NET Framework | ASP.NET | C#  | 
# Thursday, August 16, 2007

It's only a week away, and there are still spaces left.

Information   Register    Sessions

Thursday, August 16, 2007 10:42:12 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   .NET Framework | AJAX | ASP.NET | C# | Database | General | Javascript  | 
# Monday, November 06, 2006

I frequently store documents in the database for my ASP.NET apps, eliminating web farm complications with shared drives, permissions, etc.  When uploading a file, my Document class reads the uploaded file, zips the file with SharpZipLib, and inserts/updates in the database.   When opening a file, I have always used an ASPX page that uses the Document class to unzip the file, and then changes the Content-Disposition and ContentType headers, and then does a BinaryWrite to the Response object to display the file.

I have been using Handlers a lot lately, and figured that it was time to make this process a little more elegant.  If you've never written a handler, it's quite simple.  You need to make a web.config change, and add a new class that implementts IHttpHandler.  All of the work is done in ProcessRequest.  Using the default .ashx extension for the handler eliminates the need to make any changes in IIS.  I thought about changing the handler to accept all requests with known file extensions with the document ID as the filename, like 3383.pdf.  I just figured that using the default extnesion would be easier.   Laziness or efficiency, you decide.  Check out the code.

In <system.web> in web.config:


<httpHandlers>
   
<add verb="*" path="DocumentHandler.ashx" type="TestingWebApp.DocumentHandler, TestingWebApp" />
</httpHandlers>

DocumentHandler.cs:


using System;
using System.Web;

namespace TestingWebApp
{
    public class DocumentHandler : IHttpHandler
    {
        private int DocumentId
        {
            get
            {
                if(System.Web.HttpContext.Current.Request.QueryString["DocumentId"] != null && System.Web.HttpContext.Current.Request.QueryString["DocumentId"].ToString().Length > 0)
                    return Convert.ToInt32(System.Web.HttpContext.Current.Request.QueryString["DocumentId"]);
                else
                    throw new ApplicationException("Document Handler requires a DocumentId");
            }
        }

        #region IHttpHandler Members

        public void ProcessRequest(System.Web.HttpContext context)
        {
            context.Response.Cache.SetCacheability(HttpCacheability.Public);
            context.Response.BufferOutput = false;
            
            Document document = Document.GetDocumentByDocumentId(this.DocumentId);

            byte[] buffer = document.UnzippedBinary;
            context.Response.ContentType = document.ContentType;
            context.Response.OutputStream.Write(buffer, 0, buffer.Length);
        }

        public bool IsReusable
        {
            get { return true; }
        }

        #endregion
    }
}
Monday, November 06, 2006 11:31:15 PM (Eastern Standard Time, UTC-05:00)  #    Comments [5]   .NET Framework | ASP.NET | C# | Database  | 
# Tuesday, August 29, 2006
Tuesday, August 29, 2006 8:51:51 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   .NET Framework | C#  | 
# Tuesday, November 08, 2005

This 2.1 update includes over 60 improvements, including new support for .NET 2.0 and Visual Studio 2005. VistaDB is a small-footprint, embedded SQL database alternative to Jet/Access, MSDE and SQL Server Express 2005 that enables developers to build .NET 1.1 and .NET 2.0 applications. Features SQL-92 support, small 500KB embedded footprint, free 2-User VistaDB Server for remote TCP/IP data access, royalty free distribution for both embedded and server, Copy 'n Go! deployment, managed ADO.NET Provider, data management and data migration tools. Free trial is available for download.

Tuesday, November 08, 2005 9:55:01 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   .NET Framework | C# | Database  | 
# Friday, August 12, 2005

Yesterday, S. Somasegar, Corporate Vice President, Developer Division (Microsoft) shared some fantastic news, in his blog, about a new language enhancement in C# 2.0. In addition to Generics, Anonymous Methods, Iterators, and Partial Types, we will now have nullable types. Also, more details on MSDN about nullable types

Friday, August 12, 2005 8:58:43 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   .NET Framework | C#  | 
# Tuesday, August 09, 2005

I have read too many articles showing the connection being opened and closed around a DataAdapter Fill() as in the code below. It does no harm, but it not necessary.

Don't do this...

SqlCommand command = new SqlCommand("usp_MyQuery_Select", this._con);
SqlDataAdapter adapter = new SqlDataAdapter(command);
DataSet ds = new DataSet();
try
{
   this._con.Open(); //unnecessary
   adapter.Fill(ds);
}
finally
{
   this._con.Close(); //unnecessary
}

Do this...

SqlCommand command = new SqlCommand("usp_MyQuery_Select", this._con);
SqlDataAdapter adapter = new SqlDataAdapter(command);
DataSet ds = new DataSet();
adapter.Fill(ds);

If you track SqlDataAdapter's Fill() method in Lutz Roeder's .NET Reflector, you will see that it ends up at DbDataAdapter's FillFromCommand() method. FillFromCommand() opens and closes the connection in a try-finally block. Making this unnecessary in your own code.

Disassembled from Reflector

private int FillFromCommand(object data, int startRecord, int maxRecords,
   string srcTable, IDbCommand command, CommandBehavior behavior)
{
   IDbConnection connection1 = DbDataAdapter.GetConnection(command, "Fill");
   ConnectionState state1 = ConnectionState.Open;
   if (MissingSchemaAction.AddWithKey == base.MissingSchemaAction)
   {
      behavior |= CommandBehavior.KeyInfo;
   }
   int num1 = 0;
   try
   {
      try
      {
         DbDataAdapter.QuietOpen(connection1, out state1);
         using (IDataReader reader1 = command.ExecuteReader(
            behavior | CommandBehavior.SequentialAccess))
         {
            if (data is DataTable)
            {
               return this.Fill((DataTable) data, reader1);
            }
            return this.Fill((DataSet) data, srcTable, reader1, startRecord, maxRecords);
         }
      }
      finally
      {
         DbDataAdapter.QuietClose(connection1, state1);
      }
   }
   catch
   {
      throw;
   }
   return num1;
}

Tuesday, August 09, 2005 8:59:40 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   .NET Framework | C#  | 
# Friday, August 05, 2005

Most developers cringe when they hear the words "Coding Standards." A developers coding style defines them. Their style has been molded over the years into what it is today. Telling a developer that their way is not the "right way" is difficult and sometimes taken personally.

Coding standards are, however, an important part of every project. Deciding on coding standards often happens too late when there is too much code in too many different styles. It is important to every developer to have and appreciate standards because we will "inherit" someone elses code. It would be nice if we could read it.

I recently got the chance to review David McCarter's 53-page book titled "VSDN Tips & Tricks .NET Coding Standards." I loved it! It combines coding standards from Microsoft and other sources (not to mention common sense) into one easy-to-read "guide." Its focus is on Visual Studio .NET 2003 with v1.1 of the Framework. That doesn't mean that you Beta 2 snobs can't read it! It provides C# :) and VB :( examples and covers a variety of topics from indentation and word choice to event and exception handling.

I have seen most of the suggested standards in various other white papers and on MSDN. It's a great book to compare against your company or personal coding standards. Not everything in this book will make sense to every shop. Personally, I found only a few items that I don't already use (one of which being the use of Event Accessors instead of using public event member variables.)

Definitely worth reading. Find out more at http://www.vsdntips.com

Friday, August 05, 2005 9:00:17 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   .NET Framework | C#  | 
# Saturday, June 04, 2005

Devloping an application using the System.Web.Mail.SmtpMail class to send email should not be as difficult as it always is. http://www.systemwebmail.com has a detailed and helpful collection of possible fixes for the dreaded "Could not access 'CDO.Message' Object" Exception. The one I most recently experienced was not listed. We thought it could be a relaying perrmission or something related to setting the SmtpServer name. It turned out to be a Windows 2003-specific problem related to Fixed Identity Impersonation for high security (isolated) applications using a low-privilege user account specific to the application.

The exception you see, almost always the useless excpetion, "Could not access 'CDO.Message' Object", does not help you to troubleshoot. As you can see in the code below, generated using Lutz Roeder's .NET Reflector, called by SmtpMail.Send(), the real exception is never thrown. It will throw the "Could not access 'CDO.Message' Object" exception no matter what exception was caught.

internal object CallMethod(object obj, string methodName, object[] args)
{
   object obj1;
   try
   {
      obj1 = SmtpMail.LateBoundAccessHelper.CallMethod(
         this.LateBoundType, obj, methodName, args);
   }
   catch (Exception exception1)
   {
      throw new HttpException(HttpRuntime.FormatResourceString(
         "Could_not_access_object", this._progId), exception1);
   }
   return obj1;
}

The best advice given by http://www.systemwebmail.com is to loop through all of the InnerExceptions thrown when the useless exception is thrown. This will allow you to see the true exception and determine how to fix the problem.

In Windows 2003, you can have each application run in its own application pool using an application-specific user account. Depending on the privileges and group memberships of this user account, it may or may not have the correct permissions to be able to send mail using the machine's SMTP server. To send mail, the user account needs to have write permissions on the directory named C:\inetpub\mailroot\Pickup. With those permissions, System.Web.Mail (.NET wrapper around CDOSys.dll) will create a .eml file in that directory. The mail gets sent when the SMTP service, with SYSTEM privileges, sees this file and sends the email.

If granting the necessary permissions is not possible for your application's user account, you can bypass System.Web.Mail's default CDO send method, using the Pickup directory, altogether. You can modify the mail message's configuration fields to set the values used by CDO, regardless of the values set by System.Web.Mail.

You can send the mail message by going through the smtp port instead of the pickup directory (default). Change the sendusing configuration field from 0 (default - pickup directory) to 1 (port).

mailMsg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusing", 1);

If you still cannot send email, try using other CDO configuration fields to bypass the System.Web.Mail settings. Here are some other configuration fields. I would try them in this order:

// smtpauthenticate values (2=NTLM, 1=Basic, 0=None)
mailMsg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate", 1);
mailMsg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserverport", 25);
mailMsg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserver", mailServer);
mailMsg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusername", username);
mailMsg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendpassword", pw);
mailMsg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout", 10);


Of course, all of this is only possible if you are using .NET Framework 1.1. The Fields property of the MailMessage class was not exposed in version 1.0 of the framework.

Saturday, June 04, 2005 9:02:18 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   ASP.NET | C#  | 
# Saturday, October 16, 2004

DropDownLists within a Repeater appear to lose or forget which item is selected before the page loads. This is not the case. Dynamically created DropDownLists inside of a Repeater create a rare obstacle. The SelectedItem is not being lost or forgotten. It only appears that way because the OnDataBinding event for the DropDownList is being called twice. Only when the RepeaterItem containing the DropDownList calls its own DataBind method is the data actually bound to the DropDownList. Consider the following code:

// UsingDropDownLists.aspx
115 <asp:Repeater id="rptOne" runat="server" OnItemCreated="rptOne_ItemCreated"/>
116  <ItemTemplate>
117    <asp:Literal id="litQuestion" runat="server"/>
118    <asp:DropDownList id="ddlResponse" runat="server"/>
119    <br/>
120  </ItemTemplate>
121 </asp:Repeater>

// UsingDropDownLists.aspx.cs
211 protected void rptOne_ItemCreated(object sender, RepeaterItemEventArgs e)
212 {
213   if(e.Item.DataItem != null)
214   {
215     MyClass myClass = sender as MyClass;
216     (e.Item.FindControl("litQuestion") as Literal).Text = myClass.Question;
217     DropDownList ddl = e.Item.FindControl("ddlResponse") as DropDownList;
218     ddl.DataSource = GetResponseList(myClass.AvailableResponses);
219     ddl.Items.FindByValue(myClass.Response).Selected = true;
220     ddl.DataBind();
221     ddl.SelectedIndex = e.Item.ItemIndex%(ddl.Items.Count-1); //Random
222   }
223 }

Here is the sequence of events:
  1. Page_Load
  2. Data Retrieval/Bind Data to Repeater
  3. Repeater's ItemCreated event fires for each RepeaterItem
  4. Data Retrieval/Bind Data to DropDownList
  5. OnDataBinding is fired from the DropDownList's DataBind() (line 220). However, if you watch in debug mode, you will see that the DropDownList has no ListItems yet.
  6. DropDownList's SelectedIndex is chosen (line 221). Although, at this point, the DropDownList still has no ListItems. The SelectedIndex call falls on deaf ears.
  7. RepeaterItem's DataBind method performs an actual DataBind on the DropDownList. There are no selected items at this point.

RepeaterItem inherits from Control. If you look at Control's DataBind() method in Lutz Roeder's .NET Reflector, you will see that it performs a DataBind on each of its child controls. 

public virtual void DataBind()
{
   this.OnDataBinding(EventArgs.Empty);
   if (this._controls != null)
   {
      string text1 = this._controls.SetCollectionReadOnly("Parent_collections_readonly");
      int num1 = this._controls.Count;
      for (int num2 = 0; num2 < num1; num2++)
      {
         this._controls[num2].DataBind();
      }

      this._controls.SetCollectionReadOnly(text1);
   }
}

From this, we can see three things.

  1. The only safe time to choose the SelectedItem is after the binding of all objects within the Repeater. A perfect place to do this is in the RepeaterItem's PreRender or in the Repeater's PreRender. You have access to the same bound object from RepeaterItem.DataItem, and can find the DropDownList and choose the SelectedIndex, SelectedValue, or ddl.Items.FindByValue(response).Selected = true;
  2. The DataBind() call (line 220) is completely unnecessary and leads the programmer to believe that it has actually bound the data. This is untrue. Again, in debug mode, you will see that by line 221, there are still zero ListItems in ddl.Items.
  3. The same obstacle will arise for all Controls derived from ListControl. This includes CheckBoxList, DropDownList, ListBox, and RadioButtonList. The same solution will apply to these controls within a Repeater.

Saturday, October 16, 2004 9:03:32 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]   ASP.NET | C#  | 
Copyright © 2010 Scott Klueppel. All rights reserved.