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
<April 2009>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

Categories

Blogroll
Home Feed your aggregator (RSS 2.0)
# Saturday, April 11, 2009

Important, proven, and universally-useful technologies like WCF and Mobile are still not well-known in the development community. Many believe there is another boom, similar to the 1992 Internet boom, on the horizon that will require skilled and knowledgeable developers to engineer the framework and associated connecting systems. Staying tech-current and adapting to our new world is an absolute necessity. Don’t become the COBOL programmer of the future. Knowing these great system-connecting technologies (WCF, Mobile, Cloud, etc.) will quickly become a requirement to compete in the software industry.

Check out the following interview with Richard Campbell (DotNetRocks, RunAs Radio) and Juval Lowy (IDesign) talking about the EnergyNet at the DevConnections conference. The growing hype about alternative energy and the EnergyNet is stunning. Technology has progressed far enough where we can start to tie together disparate systems to benefit producers and consumers alike.

http://video.google.com/videoplay?docid=4831440850220717845

Saturday, April 11, 2009 7:51:30 AM (Eastern Standard Time, UTC-05:00)  #    Comments [4]   .NET Framework | Futures | Mobile | WCF  | 
# Wednesday, January 28, 2009

“Do not anticipate trouble, or worry about what may never happen. Keep in the sunlight.”

 

- Benjamin Franklin

 

It’s obvious that Mr. Franklin didn’t have to deal with bad data, bad code, or even the occasional hiccup. In the real world, there is “trouble” and we need to not only anticipate that trouble, but also worry about what may never happen. Simply “keeping in the sunlight” won’t maintain data or application state integrity. In the real world we need transactions!

 

Since .NET 2.0, we’ve had the privilege of using System.Transactions.TransactionScope to manage our transactions with very few headaches. One of the headaches that almost everyone experiences is MSDTC. One of the oldest and most elusive topics on the web. There are tons of blog and forum posts directing our fellow developers to check their firewall settings for every MSDTC problem. The latest MSDTC hiccup I have seen comes in the beautifully packaged error message:

The flowed transaction could not be unmarshaled. The following exception occurred: Communication with the underlying transaction manager has failed.

The what could not be what? You can read some MSDN documentation on the topic which will probably cause you more pain. If you are seeing this error message, there’s only a few things that may be wrong.

  1. MSDTC Settings
    • Check the MSDTC settings on the machine that is initiating the transaction. If “Allow Outbound” is not checked, then it won’t allow the transaction in progress to be flowed to the next machine in the transaction chain. Check the box and restart MSDTC… it should work.
  2. Un-Trusted Domains
    • I have seen this error when you are trying to flow transactions between machines that are in un-trusted domains. Machines in different domains that do not trust each other block the antiquated, yet necessary, WINS resolution between the two machines. MSDTC relies on WINS resolution. I have fixed this problem before by adding host file entries on both machines pointing to the other machine. I can’t guarantee that this will work in all cases. Both of those machines are no longer under my control.
  3. Imaged Servers
    • The most recent, and most blogged about problem is surprisingly caused by two machines created from the same image. Cloning or imaging servers is quite common. Building a server from scratch is not a fun activity, so we build one, create an image, and put that image on every server we want to build after that. Once again, MSDTC is standing in our way because of the way it detects the sending and receiving application’s unique identifier. Each machine has a GUID in the registry that identifies it uniquely as a participant in an MSDTC transaction. Imaged machines have the same GUID. I’ll talk about the detection and resolution of this for the remainder of this post.

Running DtcPing.exe between the two machines will actually tell you that the machines are using the same GUID. Output window text from DtcPing shown below.

DTCping log file: C:\DTC Ping\THRESHER4160.log
Firewall Port Settings:
Port:5150-5250
RPC server is ready
Please Start Partner DTCping before pinging
WARNING:the CID values for both test machines are the same
Please send following LOG to Microsoft for analysis:
Partner LOG: SCORPION6128.log
My LOG: THRESHER4160.log

Tucked away in the last step of a Microsoft Knowledge Base article titled "How to troubleshoot MS DTC firewall issues" is a reference to this problem. Use regedit.exe to look at the registry on both machines. Locate the HKEY_CLASSES_ROOT\CID key in the registry.

Find your MSDTC CID in the registry

Find the CID key that has a description value of “MSDTC”. If they are the same, the transaction cannot flow.

WARNING: Back up your registry before making any changes!

Solution 1 - Replace the offending CID keys/values on one of the machines: In this case, you will need to find all keys/values with GUID 28b81f1c-2afb-4ee2-ad85-5bc62dad1647 in your registry and replace it with a new GUID (using GuidGen). There is likely to be 3 places this GUID appears. It is also important to note that the offending GUID appears in the DtcPing log file generated during the ping test.

Solution 2 – Use msdtc command line tool to re-install MSDTC: The commands are simply:

msdtc -uninstall
msdtc -install

After making the registry changes or running the msdtc command line tool, you must restart your server for the changes to take effect.

Wednesday, January 28, 2009 9:39:52 PM (Eastern Standard Time, UTC-05:00)  #    Comments [7]   .NET Framework | MSDTC | Quotes | Transactions | WCF  | 
# 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  | 
Copyright © 2010 Scott Klueppel. All rights reserved.