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 (31) AJAX (9) ASP.NET (16) ASP.NET MVC (3) C# (32) Cloud (2) Database (6) Dev Community (2) Dev Tools (5) Enterprise Library (1) Futures (2) General (6) IIS (1) Javascript (7) LINQ (2) Mobile (1) MSDTC (5) Quotes (3) SQL (3) Transactions (4) Visual Studio (3) WAS (2) WCF (20) WIF (1)

Archive
<January 2009>
SunMonTueWedThuFriSat
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567

Categories

Blogroll
Home Feed your aggregator (RSS 2.0)
# Sunday, January 04, 2009

"To achieve great things, two things are needed; a plan, and not quite enough time."

 

- Leonard Bernstein

Sunday, January 04, 2009 8:59:30 PM (Eastern Standard Time, UTC-05:00)  #    Comments [9]   Quotes  | 
# 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  | 
# Saturday, September 06, 2008

I really need to read up on new features when a major release comes out. Just a few weeks ago I learned of a great "new" SQL 2005 function... ROW_NUMBER(). Just in time since SQL 2008 is already out.

For me, this function means a lot less temp tables. I would typically create a temp table with an ID INT IDENTITY(1,1) column to create an DisplayOrder, BatchID, etc. used to group or join on later. Books Online describes the function as "Returns the sequential number of a row within a partition of a result set, starting at 1 for the first row in each partition." The syntax is simple, and looks like:

ROW_NUMBER() OVER (ORDER BY ID DESC)

For this example, the data I want to bring back with a DisplayOrder column looks like:

pers_subs_data

Without ROW_NUMBER(), using a table variable with an identity column:

DECLARE @Subs TABLE (DisplayOrder INT IDENTITY(1,1), [Address] VARCHAR(100), Operation VARCHAR(50), [Contract] VARCHAR(50))

INSERT INTO @Subs ([Address], Operation, [Contract])
SELECT [Address]
    , Operation
    , [Contract]
FROM PersistentSubscribers
WHERE Operation = 'OnEvent2'
ORDER BY ID DESC

SELECT * FROM @Subs

With ROW_NUMBER(), look how beautiful:

SELECT DisplayOrder = ROW_NUMBER() OVER (ORDER BY ID DESC)
    , [Address]
    , Operation
    , [Contract]
FROM PersistentSubscribers
WHERE Operation = 'OnEvent2'

The results from both methods looks like:

 

row_number_results

Saturday, September 06, 2008 10:33:25 PM (Eastern Standard Time, UTC-05:00)  #    Comments [8]   SQL  | 
Copyright © 2010 Scott Klueppel. All rights reserved.