Protecting WCF Service Over Internet : Authenticating and Authorizing Users by Using the ASP.NET Membership Provider and Role Provider

Table of Contents

  • WCF Security – Basics
  • Background
  • Configure IIS Bindings to Support SSL
  • Create ProductsService Web Application
  • Create an ASP.NET Web Site to Host the WCF Service
  • Define Users and Roles for the WCF Service
  • Sample Code for WCF Service
  • Configure the Activation and Binding for the WCF Service
  • Modify the WCF Client Application to Connect to the Updated WCF Service
  • Test the WCF Service
  • Summary
  • References

WCF Security – Basics

WCF security is divided into three functional areas: transfer security, access control, and auditing.

Transfer Security

Transfer security encompasses three major security functions: integrity, confidentiality, and authentication. Integrity is the ability to detect whether a message has been tampered with. Confidentiality is the ability to keep a message unreadable by anyone other than the intended recipient; this is achieved through cryptography. Authentication is the ability to verify a claimed identity.

Together, these three functions help to ensure that messages securely arrive from one point to another.

Two main mechanisms are used to implement transfer security in WCF: transport security mode and message security mode.

1. Transport security mode uses a transport-level protocol, such as HTTPS, to achieve transfer security. It has the disadvantage of securing messages only from point-to-point.

2. Message security mode uses WS-Security (and other specifications) to implement transfer security. Because the message security is applied directly to the SOAP messages and is contained inside the SOAP envelopes, together with the application data, it has the advantage of being transport protocol-independent, more extensible, and ensuring end-to-end security (versus point-to-point); it has the disadvantage of being several times slower than transport security mode because it has to deal with the XML nature of the SOAP messages.

3. TransportWithMessageCredential message security is used to authenticate the client and transport security is used to authenticate the server and provide message confidentiality and integrity.

Access Control

Access control is also known as authorization. Authorization allows different users to have different privileges to view data.

In WCF, access control features are provided through integration with the common language runtime (CLR) PrincipalPermissionAttribute and through a set of APIs known as the identity model.

Auditing

Auditing is the logging of security events to the Windows event log. You can log security-related events, such as authentication failures or successes.

For more information, click here

Background

When you start connecting client applications and services across a public network such as the Internet, you can no longer make any guarantee that client applications are always genuine.

Few questions comes in mind:

How does a client application verify that the service to which it is sending messages is the real service and not?

How does a service distinguish genuine requests sent by an authenticated client application from those generated by an attacker?

Maintaining information about the users who can legitimately access a service and their credentials typically requires some form of database.

In a Windows environment, Active Directory provides such a database. A WCF service can use Windows Integrated Security to help authenticate users who are part of the same Windows domain as the service. When client applications connect to the service across the Internet, this approach is not always feasible; a client application will probably not be running using the same security domain as the service or it might not even be a Windows application.

You can use several alternative approaches for maintaining a list of authenticated users for a WCF service. For example, you can employ the ASP.NET Membership Provider (to store a list of users and their credentials in a SQL Server database) together with the ASP.NET Role Provider (to associate users with roles). In this article we will see how it works.

To make a WCF service available across the Internet, you would typically host it by using IIS.

By hosting a WCF service using IIS, you can use the ASP.NET Web Site Administration Tool to easily create a SQL Server database containing the security information for the service and, manage users and roles. You can then configure the WCF service to use the ASP.NET Membership Provider to authenticate users, and the ASP.NET Role Provider to retrieve role information for authorizing users.

Configure IIS Bindings to Support SSL

It is good practice to use the Secure Sockets Layer (SSL) to protect communications when you build a service that is exposed to the Internet. Therefore, the first task is to configure IIS to support SSL by adding a certificate to identify the service and encrypt information passing to and from client applications. You can then bind this certificate to the Web site that hosts your WCF services. Below are the steps to configure IIS bindings to support SSL:

1. Open the Internet Information Services Manager console as an administrator.

2. In the Internet Information Services (IIS) Manager console window, in the Connections pane, click the node that corresponds to your computer.

3. In the middle pane, click the Features View tab.

4. In the Features View pane, in the IIS section, double-click Server Certificates. The Server Certificates pane appears, displaying possible certificates that you can use to configure IIS to use to implement SSL.

5. In the Actions pane, click Create Self-Signed Certificate. The Create Self-Signed Certificate dialog box appears. You should not use a self-signed certificate in a production environment. If you require a commercial-grade certificate, you should click the Create Certificate Request link in the Actions pane, create a certificate request that identifies your organization, and then send this request to a certificate provider, such as Active Directory Certificate Services or a trusted third-party organization to generate the certificate.

6. In the Create Self-Signed Certificate dialog box, in the Specify A Friendly Name For The Certificate box, type the name of your computer, and then click OK.

Note It is important that you give the certificate a friendly name that is the same as your computer, otherwise you may run into some security errors later on when you attempt to access the Web site.

7. In the Connections pane, expand the node that corresponds to your computer, expand Sites, right-click Default Web Site, and then click Edit Bindings. The Site Bindings dialog box appears, as shown in the following image, listing the protocols that IIS and WAS support for the Web site.

8. In the Site Bindings dialog box, if https is not configured, click Add; otherwise, select https, and then click edit.

9. In the Add (or Edit) Site Binding dialog box, set Type to https, select the SSL certificate that is named after your computer, and then click OK.

10. Click Close.

Create the ProductsService Web Application

1. Start Windows Explorer as an administrator and create the following folder:
C:\inetpub\wwwroot\ProductsService

2. Return to IIS Manager console. In the Connections pane, right-click Default Web Site, and then click Add Application.

3. In the Add Application dialog box, specify the values shown in the following table, and then click OK.

———————————————————————————–
Item                           Value
————————————————————————————–
Alias                           ProductsService
Application Pool      ASP.NET v4.0
Physical Path           C:\inetpub\wwwroot\ProductsService
————————————————————————————–

4. In the Connections pane, click the ProductsService application.

5. In the middle pane, click the Features View tab, and then double-click SSL Settings in the IIS section.

6. In the SSL Settings pane, check Require SSL, and then in the Actions pane, click Apply.

Create ASP.NET Web Site to Host the WCF Service

1. Using Visual Studio, create a new Web site.

2. In the New Web Site dialog box, click the WCF Service template. Set the Web location to HTTP, and type https://YourComputer/ProductsService, where YourComputer is the name of your computer, and then click OK.

Note Make sure that you specify https as the scheme in this address.

3. In Solution Explorer, delete the Service.svc file and the Web.config file, and the IService.cs and Service.cs files in the App_Code folder.

Note Although the Web site is configured to use SSL and support transport-level security, you can still perform message-level encryption as well if you need to provide end-to-end security rather than point-to-point. However, remember that encryption is a necessarily expensive operation. Encrypting at two levels will impact performance. Transport-level encryption tends to be much faster than message-level encryption. So, if performance is a limiting factor and you have to make a choice, go for transport-level security.

Define Users and Roles for the WCF Service

1. In Visual Studio, from the Website menu, choose ASP.NET Configuration. The ASP.NET Web Site Administration Tool starts. This is actually another Web application that runs by using the ASP.NET Development Server.

This tool provides pages with which you can add and manage users for your Web site, specify Web application settings that you want to be stored in the application configuration file (not WCF settings), and indicate how security information such as user names and passwords are stored. By default, the ASP.NET Web Site Administration Tool stores security information in a local SQL Server database called ASPNETDB.MDF that it creates in the App_Data folder of your Web site.

2. Click the Security tab.

You can use this page to manage users, specify the authentication mechanism that the Web site uses, define roles for users, and specify access rules for controlling access to the Web site.

3. In the Users section of the page, click the Select Authentication Type link. A new page appears asking how users will access your Web site. You have two options available:

  • From The Internet: You can define users and roles in the SQL Server database. Users accessing your application must provide an identity that maps to a valid user.
  • From A Local Network: This option is selected by default. It configures the Web site to use Windows authentication; all users must be members of a Windows domain that your Web site can access.

4. Select the From The Internet option, and then click Done.

5. In the Users section, notice that the number of existing users that can access your Web site is currently zero. Click the Create User link.

6. In the Create User page, add a new user. {User Name : Sandeep}

7. Ensure that the Active User check box is selected, and then click Create User.

8. Click Continue. The Create User page reappears, in which you can add more users. {User Name : Ajit}

9. Again, ensure that the Active User check box is selected, and then click Create User.

10. Click Back to return to the Security page. Verify that the number of existing users is now set to 2.

11. In the Roles section of the page, click the Enable Roles link.

12. When roles have been enabled, click the Create Or Manage roles link.

13. In the New Role Name text box, type WarehouseStaff, and then click Add Role. The new role appears on the page, together with links which you can use to add and remove users to or from this role.

14. Click the Manage link. Here you can specify the users that are members of this role.

15. Select the User Is In Role check box for Sandeep and Ajit.

16. Then click Back.

17. In the Create New Role page, in the New Role Name text box, type StockControllers, and then click Add Role.

18. Click the Manage link for the StockControllers role. Add Ajit to the StockControllers role and then click Back.

19. Close the ASP.NET Web Site Administration Tool.

The next step is to modify the behavior of the WCF service to perform authorization by using the users and roles defined in the SQL Server database created by the ASP.NET Role Provider and the Membership Provider, rather than by using Windows users and groups.

Sample Code for the WCF Service

Right-click the App_Code folder, and then click Add New class file with filename ProductsService.cs, and then click Add. Repeat this process for the IProductsService.cs file. The ProductsService.cs file contains the code for the ProductsService service.

Note Code in these files are just for reference purpose not complete code. you can use your existing application code for WCF Service and modify it.

ProductService.cs

     // WCF Service that implements the service contract
    // This implementation performs minimal error checking and exception handling
    public class ProductsServiceImpl : IProductsService
    {
        [PrincipalPermission(SecurityAction.Demand, Role="WarehouseStaff")]
        public List ListProducts()
        {

            // Create a list for holding product numbers
            List productsList = new List();

            try
            {
                // Connect to the AdventureWorks database by using the Entity Framework
                using (AdventureWorksEntities database = new AdventureWorksEntities())
                {
                    /// Fetch the product number of every product in the database
                    var products = from product in database.Products
                                   select product.ProductNumber;

                    productsList = products.ToList();
                }
            }
            catch (Exception e)
            {
                // Edit the Initial Catalog in the connection string in app.config to trigger this exception
                if (e.InnerException is System.Data.SqlClient.SqlException)
                {
                    DatabaseFault dbf = new DatabaseFault
                    {
                        DbOperation = "Connect to database",
                        DbReason = "Exception accessing database",
                        DbMessage = e.InnerException.Message
                    };

                    throw new FaultException(dbf);
                }
                else
                {
                    SystemFault sf = new SystemFault
                    {
                        SystemOperation = "Iterate through products",
                        SystemReason = "Exception reading product numbers",
                        SystemMessage = e.Message
                    };

                    throw new FaultException(sf);
                }
            }

            // Return the list of product numbers
            return productsList;
        }

        [PrincipalPermission(SecurityAction.Demand, Role = "WarehouseStaff")]
        public ProductData GetProduct(string productNumber)
        {
            // Create a reference to a ProductData object
            // This object will be instantiated if a matching product is found
            ProductData productData = null;

            try
            {
                // Connect to the AdventureWorks database by using the Entity Framework
                using (AdventureWorksEntities database = new AdventureWorksEntities())
                {
                    // Find the first product that matches the specified product number
                    Product matchingProduct = database.Products.First(
                        p => String.Compare(p.ProductNumber, productNumber) == 0);

                    productData = new ProductData()
                    {
                        Name = matchingProduct.Name,
                        ProductNumber = matchingProduct.ProductNumber,
                        Color = matchingProduct.Color,
                        ListPrice = matchingProduct.ListPrice
                    };
                }
            }
            catch
            {
                // Ignore exceptions in this implementation
            }

            // Return the product
            return productData;
        }

        [PrincipalPermission(SecurityAction.Demand, Role = "WarehouseStaff")]
        public int CurrentStockLevel(string productNumber)
        {
            // Obtain the total stock level for the specified product.
            // The stock level is calculated by summing the quantity of the product
            // available in all the bins in the ProductInventory table.

            // The Product and ProductInventory tables are joined over the
            // ProductID column.

            int stockLevel = 0;

            try
            {
                // Connect to the AdventureWorks database by using the Entity Framework
                using (AdventureWorksEntities database = new AdventureWorksEntities())
                {
                    // Calculate the sum of all quantities for the specified product
                    stockLevel = (from pi in database.ProductInventories
                                  join p in database.Products
                                  on pi.ProductID equals p.ProductID
                                  where String.Compare(p.ProductNumber, productNumber) == 0
                                  select (int)pi.Quantity).Sum();
                }
            }
            catch
            {
                // Ignore exceptions in this implementation
            }

            // Return the stock level
            return stockLevel;
        }

        public bool ChangeStockLevel(string productNumber, short newStockLevel, string shelf, int bin)
        {
            // Determine whether the user is a member of the StockControllers role
            WindowsPrincipal user = new WindowsPrincipal((WindowsIdentity)Thread.CurrentPrincipal.Identity);
            if (!(user.IsInRole("StockControllers")))
            {
                // If the user is not in the StockControllers role,
                // throw a SecurityException
                throw new SecurityException("Access denied");
            }

            // Modify the current stock level of the selected product
            // in the ProductInventory table.
            // If the update is successful then return true, otherwise return false.

            // The Product and ProductInventory tables are joined over the
            // ProductID column.

            try
            {
                // Connect to the AdventureWorks database by using the Entity Framework
                using (AdventureWorksEntities database = new AdventureWorksEntities())
                {
                    // Find the ProductID for the specified product

                    int productID =
                        (from p in database.Products
                         where String.Compare(p.ProductNumber, productNumber) == 0
                         select p.ProductID).First();

                    // Find the ProductInventory object that matches the parameters passed
                    // in to the operation
                    ProductInventory productInventory = database.ProductInventories.First(
                        pi => String.Compare(pi.Shelf, shelf) == 0 && pi.Bin == bin &&
                              pi.ProductID == productID);

                    // Update the stock level for the ProductInventory object
                    productInventory.Quantity += newStockLevel;

                    // Save the change back to the database
                    database.SaveChanges();
                }
            }
            catch
            {
                // If an exception occurs, return false to indicate failure
                return false;
            }

            // Return true to indicate success
            return true;
        }
    }

IProductsService.cs :

    // Classes for passing fault information back to client applications
    [DataContract]
    public class SystemFault
    {
        [DataMember]
        public string SystemOperation { get; set; }

        [DataMember]
        public string SystemReason { get; set; }

        [DataMember]
        public string SystemMessage { get; set; }
    }

    [DataContract]
    public class DatabaseFault
    {
        [DataMember]
        public string DbOperation { get; set; }

        [DataMember]
        public string DbReason { get; set; }

        [DataMember]
        public string DbMessage { get; set; }
    }

    // Data contract describing the details of a product passed to client applications
    [DataContract]
    public class ProductData
    {
        [DataMember]
        public string Name;

        [DataMember]
        public string ProductNumber;

        [DataMember]
        public string Color;

        [DataMember]
        public decimal ListPrice;
    }

    // Service contract describing the operations provided by the WCF service
    [ServiceContract]
    public interface IProductsService
    {
        // Get the product number of every product
        [FaultContract(typeof(SystemFault))]
        [FaultContract(typeof(DatabaseFault))]
        [OperationContract]
        List ListProducts();

        // Get the details of a single product
        [OperationContract]
        ProductData GetProduct(string productNumber);

        // Get the current stock level for a product
        [OperationContract]
        int CurrentStockLevel(string productNumber);

        // Change the stock level for a product
        [OperationContract]
        bool ChangeStockLevel(string productNumber, short newStockLevel, string shelf, int bin);
    }

For this service, you will use a WS2007HttpBinding binding that supports transport-level security for protecting messages, but with message-level credentials for authenticating and authorizing because this is the level at which the ASP.NET Role Provider operates. You will implement configuration-based activation for the service so that you do not need to add a .svc file.

Configure the Activation and Binding for the WCF Service

Open Web.Config file in WCF Service – ProductsService project and update configuration details as per below:

Web.Config

<configuration>
	....
 <system.web>
	<roleManager enabled="true" />
	<authentication mode="Forms" />
	.............
 </system.web>

 <system.serviceModel>

  <bindings>
     <ws2007HttpBinding>
	<binding name="ProductsServiceWS2007HttpBindingConfig">
	     <security mode="TransportWithMessageCredential">
	          <transport clientCredentialType="None" />
	          <message clientCredentialType="UserName" />
	     </security>
	</binding>
      </ws2007HttpBinding>
   </bindings>

  <services>
   <service name="Products.ProductsServiceImpl">
        <endpoint address="https://localhost/ProductsService/Service.svc"
                         binding="ws2007HttpBinding"
                         bindingConfiguration="ProductsServiceWS2007HttpBindingConfig"
                         contract="Products.IProductsService" />
    </service>
  </services>

  <behaviors>
   <serviceBehaviors>
       <behavior name="">
             <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
             <serviceDebug includeExceptionDetailInFaults="false" />
             <serviceAuthorization principalPermissionMode="UseAspNetRoles"
                       roleProviderName="AspNetSqlRoleProvider" />
             <serviceCredentials>
                  <userNameAuthentication userNamePasswordValidationMode="MembershipProvider"
                           membershipProviderName="AspNetSqlMembershipProvider" />
             </serviceCredentials>
       </behavior>
   </serviceBehaviors>
  </behaviors>

  <serviceHostingEnvironment multipleSiteBindingsEnabled="false">
       <serviceActivations>
            <add relativeAddress="Service.svc" service="Products.ProductsServiceImpl" />
       </serviceActivations>
  </serviceHostingEnvironment>

 </system.serviceModel>

  <system.webServer>
      <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>

</configuration>

The host Web site is configured to use the HTTPS protocol, so the WCF service must be configured to support transport-level security. The TransportWithMessageCredential mode uses HTTPS at the transport level to protect messages traversing the network and uses the server certificate to authenticate with the client. The user’s credentials are authenticated by using message-level security. The credentials are passed as a UserName token.

If you are using the HTTPS protocol and you wish to enable the service to publish metadata, you must set the HttpsGetEnabled property of the serviceMetadata behavior to True. Additionally, you cannot set both the HttpGetEnabled and the HttpsGetEnabled properties to True at the same time (either the service is using HTTPS or it isn’t).

The RoleProviderName property identifies a particular configuration for the identity role provider that will be used to map users to roles. The value “AspNetSqlRoleProvider” is actually defined in the Machine.config file and specifies the version of the ASP.NET Role Provider to use to authorize users, together with information, about how to connect to the database holding the user and role information.

The membership provider is responsible for authenticating users based on their names and passwords stored in the SQL Server database. The value “AspNetSqlMembership Provider” is also defined in the Machine.config file.

Note Try not to get too confused by the role provider and the membership provider. WCF uses the membership provider for authenticating users, and it uses the role provider for authorizing users’ access to resources after they have been authenticated.

To quickly test that you have configured the service correctly, start Internet Explorer and go to the Web site https://YourComputer/ProductsService/Service.svc (where YourComputer is the name of your computer).

Note If your computer is joined to a Windows domain, you may need to specify the fully qualified name of your computer in Internet Explorer. This has the form YourComputer.Your-Domain and may include an extension such as “.net” or “.com”. If you are not sure of the fully qualified name of your computer, consult the system administrator who manages your domain. Internet Explorer opens the page https://YourComputer/ProductsService/ProductsService.svc

Don’t Forget to check this:

Depending on how you have configured the application pool used by the ProductsService Web application in IIS, you may need to amend the identity used by the application pool. The default configuration of the ASP.NET v4.0 application pool will result in a failure when the WCF runtime attempts to access the SQL Server membership database (ASPNETDB.MDF in the App_Data folder of the Web application).

Rather confusingly, the error is reported by the WCF runtime as “An unsecured or incorrectly secured fault was received from the other party,” but if you examine the Windows Application Event Log you will find an exception with the message “Failed to generate a user instance of SQL Server due to a failure in retrieving the user’s local application data path. Please make sure the user has a local profile on the computer.” To circumvent this problem, you can run the ASP.NET v4.0 application pool with the NETWORK SERVICE identity, as follows:

1. IIS Manager console, in the Connections pane, click Application Pools.

2. In the Application Pools pane, right-click the ASP.NET v4.0 application pool, and then click Advanced Settings.

3. In the Advanced Settings dialog box, in the Process Model section, click Identity, and then click the ellipsis button that appears on the right-hand side.

4. In the Application Pool Identity dialog box, click Built-In Account, select NetworkService from the drop-down list, and then click OK.

You must make some changes so that the client application connects to the WCF service by using the correct binding and address.

Modify the WCF Client Application to Connect to the Updated WCF Service

Note we are assuming that we have sample client application ready with us and we just need to modify it for our purpose.

1. In Solution Explorer, open the Program.cs file in the ProductClient project. Modify the statement that creates the proxy to refer to the WS2007HttpBinding_IProductsService endpoint, as shown in the following:

ProductClient —> Program.cs:

static void Main(string[] args)
    {
        Console.WriteLine("Press ENTER when the service has started");
	Console.ReadLine();

	// Create a proxy object and connect to the service
	PermissiveCertificatePolicy.Enact("CN=SandyWorldDesktop");
	ProductsServiceClient proxy = new ProductsServiceClient("WS2007HttpBinding_IProductsService");

        proxy.ClientCredentials.UserName.UserName = "Sandeep";
	proxy.ClientCredentials.UserName.Password = "Password";

         // Test the operations in the service
         // Obtain a list of all products
         Console.WriteLine("Test 1: List all products");
         string[] productNumbers = proxy.ListProducts();
         foreach (string productNumber in productNumbers)
         {
              Console.WriteLine("Number: {0}", productNumber);
         }
         Console.WriteLine();

         Console.WriteLine("Test 2: Display the details of a product");
         ProductData product = proxy.GetProduct("WB-H098");
         Console.WriteLine("Number: {0}", product.ProductNumber);
         Console.WriteLine("Name: {0}", product.Name);
         Console.WriteLine("Color: {0}", product.Color);
         Console.WriteLine("Price: {0}", product.ListPrice);
         Console.WriteLine();

         // Query the stock level of this product
         Console.WriteLine("Test 3: Display the stock level of a product");
         int numInStock = proxy.CurrentStockLevel("WB-H098");
         Console.WriteLine("Current stock level: {0}", numInStock);
         Console.WriteLine();

         // Modify the stock level of this product
         Console.WriteLine("Test 4: Modify the stock level of a product");
         if (proxy.ChangeStockLevel("WB-H098", 100, "N/A", 0))
         {
               numInStock = proxy.CurrentStockLevel("WB-H098");
               Console.WriteLine("Stock changed. Current stock level: {0}", numInStock);
         }
         else
         {
              Console.WriteLine("Stock level update failed");
         }
         Console.WriteLine();

         // Disconnect from the service
         proxy.Close();

         Console.WriteLine("Press ENTER to finish");
         Console.ReadLine();
     }

The client application uses message-level authentication to send the user’s credentials to the WCF service. You specify the credentials to send by using the ClientCredentials.UserName property of the proxy object.

2. Open the app.config file for the ProductClient project and add new binding configuration and new endpoint as per below:

<configuration>
    <system.serviceModel>
        <bindings>

            <ws2007HttpBinding>
                <binding name="ProductsClientWS2007HttpBindingConfig">
                    <security mode="TransportWithMessageCredential">
                        <transport clientCredentialType="None" />
                        <message clientCredentialType="UserName" />
                    </security>
                </binding>
            </ws2007HttpBinding>
        </bindings>
        <client>

            <endpoint address="https://localhost/ProductsService/Service.svc"
                binding="ws2007HttpBinding" bindingConfiguration="ProductsClientWS2007HttpBindingConfig"
                contract="ProductsClient.ProductsService.IProductsService"
                name="WS2007HttpBinding_IProductsService" />
        </client>
    </system.serviceModel>
</configuration>

3. Save the configuration file.

Test the WCF Service

1. Run the ProductClient project.

2. When the client console window appears, press Enter to connect to the service. The first three tests should run successfully, but the final test fails with the error.

The PrincipalPermission attributes implementing security demands for the first three methods automatically use the currently configured role provider.

The problem is that the method executed by Test 4 does not use the PrincipalPermission attribute—the authorization check is performed by using code. In particular, the following statement attempts to retrieve the identity of the user assuming it was a Windows principal, which it no longer is:

WindowsPrincipal user = new WindowsPrincipal((WindowsIdentity)Thread.CurrentPrincipal.Identity);

3. Edit the ProductsService.cs file. Locate the ChangeStockLevel method and modify the two lines of code that create the user variable; test this variable to determine whether the user is a member of the StockControllers role, as shown in bold in the following:

public bool ChangeStockLevel(string productNumber, short newStockLevel, string shelf, int bin)
{
	// Determine whether the user is a member of the StockControllers role
	IIdentity user = ServiceSecurityContext.Current.PrimaryIdentity;
	if (!(System.Web.Security.Roles.IsUserInRole(user.Name, "StockControllers")))
	{
		...
	}
	...
}

The ServiceSecurityContext class contains information about the current security context for the WCF operation being performed. This security context information includes the identity if the user requesting the operation, which is available in the static Current. PrimaryIdentity property. You can use the name held in this identity object to determine whether the user is a member of a specific role by using the IsInRole method of the
System.Web.Security.Roles class. The Roles class accesses the data in the currently configured role provider for the WCF service to perform its work.

5. Run Client application. Press Enter when the client application window appears. This time, Test 4 fails with the error “Access is denied.” This is because Sandeep is not a member of the StockControllers role.

6. Edit the Program.cs file in the ProductClient project. Change the user name sent to the WCF service through the proxy as follows:

proxy.ClientCredentials.UserName.UserName = "Ajit";

7. Run Client application. Press Enter when the client application window appears. Ajit is a member of both the WarehouseStaff and StockControllers roles, and all tests should run successfully.

Summary

The ASP.NET role provider enables ASP.NET developers to create Web sites that allow users to create an account with a site and assign roles for authorization purpose. With this feature, any user can establish an account with the site, and log in for access to the site and its services. This is in contrast to Windows security, which requires users to have accounts in a Windows domain. Instead, any user who supplies his or her credentials (the user name/password combination) can use the site and its services.

The role provider feature uses a SQL Server database to store user information. Windows Communication Foundation (WCF) developers can take advantage of these features for security purposes. When integrated into a WCF application, users must supply a user name/password combination to the WCF client application.

References :

1) Use the ASP.NET Role Provider with a Service

2) Using ASP.NET Membership Provider authentication in a WCF service

3) Chapter 5 of WCF Step By Step Book

Hope this will help !!!

Jay Ganesh

Export to Excel – Multiple GridView into Multiple Worksheet

There are many article available on web which explain us functionality “Export gridview data into excel“. Which is like “Exporting single gridview data into excel workbook with single worksheet“.

What-if we want to “Export multiple gridview data into multiple worksheets”. So I have decided to write article which explain such scenario.

The focus of the article is Export to Excel functionality – Gridview and its data binding are only for demonstrating the Export functionality.

Here it goes step-by-step code snippets which will help us to achieve the functionality mentioned above.

Step 1: Create simple website and add two gridviews like this:

<form id="form1" runat="server">
<div>
	<h1>Patient Data</h1>
	<br />
	<asp:GridView ID="gvPatient" runat="server">
	</asp:GridView>
	<h1>Student Data</h1>
	<br />
	<asp:GridView ID="gvStudent" runat="server">
	</asp:GridView>
	<br />
	<asp:Button ID="btnExportBoth" runat="server"
		Text="Export Both Grid Data to Excel" onclick="btnExportBoth_Click" />
</div>
</form>

Step 2: Create properties which help us to store the Datatable data.

/// <summary>
/// Property to store patient datatable
/// </summary>
public DataTable PatientData
{
   get { return (DataTable)(ViewState["PatientData"] ?? null); }
   set { ViewState["PatientData"] = value; }
}

/// <summary>
/// Property to store student datatable
/// </summary>
public DataTable StudentData
{
   get { return (DataTable)(ViewState["StudentData"] ?? null); }
   set { ViewState["StudentData"] = value; }
}

Step 3: Page_Load event will load the sample data in Gridview on the page [Patient and Student data respectively]

protected void Page_Load(object sender, System.EventArgs e)
{
	DataTable dt = new DataTable();
	//Patient Data Binding
	dt = GetPatientData();
	gvPatient.DataSource = dt;
	gvPatient.DataBind();
	PatientData = dt;
	//Student Data Binding
	dt = GetStudentData();
	gvStudent.DataSource = dt;
	gvStudent.DataBind();
	StudentData = dt;
}

/// <summary>
/// Get Patient Data - Sample Data creation
/// </summary>
/// <returns></returns>
private DataTable GetPatientData()
{

	// Here we create a DataTable with four columns.
	DataTable table = new DataTable("Patients");
	table.Columns.Add("Dosage", typeof(int));
	table.Columns.Add("Drug", typeof(string));
	table.Columns.Add("Patient", typeof(string));
	table.Columns.Add("Date", typeof(DateTime));

	// Here we add five DataRows.
	table.Rows.Add(25, "Indocin", "David", DateTime.Now);
	table.Rows.Add(50, "Enebrel", "Sam", DateTime.Now);
	table.Rows.Add(10, "Hydralazine", "Christoff", DateTime.Now);
	table.Rows.Add(21, "Combivent", "Janet", DateTime.Now);
	table.Rows.Add(100, "Dilantin", "Melanie", DateTime.Now);
	return table;
}

/// <summary>
/// Get Student Data - Sample Data creation
/// </summary>
/// <returns></returns>
private DataTable GetStudentData()
{

	// Here we create a DataTable with four columns.
	DataTable table = new DataTable("Students");
	table.Columns.Add("SrNo", typeof(int));
	table.Columns.Add("FirstName", typeof(string));
	table.Columns.Add("LastName", typeof(string));
	table.Columns.Add("Age", typeof(int));

	// Here we add five DataRows.
	table.Rows.Add(1, "Sandeep", "Ramani", 29);
	table.Rows.Add(2, "Kapil", "Bhaai", 28);
	table.Rows.Add(3, "Vinit", "Shah", 28);
	table.Rows.Add(4, "Samir", "Bhaai", 30);
	table.Rows.Add(5, "Umang", "Samani", 29);
	return table;
}

Step 4: Here comes the main part which we are waiting for. Export button click event logic along with methods required to create workbook and worksheets.

/// <summary>
/// Export button click event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void btnExportBoth_Click(object sender, EventArgs e)
{
    object[] myGridViews = new object[2];
    myGridViews[0] = PatientData;
    myGridViews[1] = StudentData;
    CreateWorkBook(myGridViews, "ExportToExcel", 80);
}

/// <summary>
/// Method to create workbook
/// </summary>
/// <param name="cList"></param>
/// <param name="wbName"></param>
/// <param name="CellWidth"></param>
private void CreateWorkBook(object[] cList, string wbName, int CellWidth)
{
   string attachment = "attachment; filename=\"" + wbName + ".xml\"";
   HttpContext.Current.Response.ClearContent();
   HttpContext.Current.Response.AddHeader("content-disposition", attachment);
   HttpContext.Current.Response.ContentType = "application/ms-excel";
   System.IO.StringWriter sw = new System.IO.StringWriter();
   sw.WriteLine("<?xml version=\"1.0\"?>");
   sw.WriteLine("<?mso-application progid=\"Excel.Sheet\"?>");
   sw.WriteLine("<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"");
   sw.WriteLine("xmlns:o=\"urn:schemas-microsoft-com:office:office\"");
   sw.WriteLine("xmlns:x=\"urn:schemas-microsoft-com:office:excel\"");
   sw.WriteLine("xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\"");
   sw.WriteLine("xmlns:html=\"http://www.w3.org/TR/REC-html40\">");
   sw.WriteLine("<DocumentProperties xmlns=\"urn:schemas-microsoft-com:office:office\">");
   sw.WriteLine("<LastAuthor>Try Not Catch</LastAuthor>");
   sw.WriteLine("<Created>2013-01-09T19:14:19Z</Created>");
   sw.WriteLine("<Version>11.9999</Version>");
   sw.WriteLine("</DocumentProperties>");
   sw.WriteLine("<ExcelWorkbook xmlns=\"urn:schemas-microsoft-com:office:excel\">");
   sw.WriteLine("<WindowHeight>9210</WindowHeight>");
   sw.WriteLine("<WindowWidth>19035</WindowWidth>");
   sw.WriteLine("<WindowTopX>0</WindowTopX>");
   sw.WriteLine("<WindowTopY>90</WindowTopY>");
   sw.WriteLine("<ProtectStructure>False</ProtectStructure>");
   sw.WriteLine("<ProtectWindows>False</ProtectWindows>");
   sw.WriteLine("</ExcelWorkbook>");
   sw.WriteLine("<Styles>");
   sw.WriteLine("<Style ss:ID=\"Default\" ss:Name=\"Normal\">");
   sw.WriteLine("<Alignment ss:Vertical=\"Bottom\"/>");
   sw.WriteLine("<Borders/>");
   sw.WriteLine("<Font/>");
   sw.WriteLine("<Interior/>");
   sw.WriteLine("<NumberFormat/>");
   sw.WriteLine("<Protection/>");
   sw.WriteLine("</Style>");
   sw.WriteLine("<Style ss:ID=\"s22\">");
   sw.WriteLine("<Alignment ss:Horizontal=\"Center\" ss:Vertical=\"Center\" ss:WrapText=\"1\"/>");
   sw.WriteLine("<Borders>");
   sw.WriteLine("<Border ss:Position=\"Bottom\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"");
   sw.WriteLine("ss:Color=\"#000000\"/>");
   sw.WriteLine("<Border ss:Position=\"Left\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"");
   sw.WriteLine("ss:Color=\"#000000\"/>");
   sw.WriteLine("<Border ss:Position=\"Right\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"");
   sw.WriteLine("ss:Color=\"#000000\"/>");
   sw.WriteLine("<Border ss:Position=\"Top\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"");
   sw.WriteLine("ss:Color=\"#000000\"/>");
   sw.WriteLine("</Borders>");
   sw.WriteLine("<Font ss:Bold=\"1\"/>");
   sw.WriteLine("</Style>");
   sw.WriteLine("<Style ss:ID=\"s23\">");
   sw.WriteLine("<Alignment ss:Vertical=\"Bottom\" ss:WrapText=\"1\"/>");
   sw.WriteLine("<Borders>");
   sw.WriteLine("<Border ss:Position=\"Bottom\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"");
   sw.WriteLine("ss:Color=\"#000000\"/>");
   sw.WriteLine("<Border ss:Position=\"Left\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"");
   sw.WriteLine("ss:Color=\"#000000\"/>");
   sw.WriteLine("<Border ss:Position=\"Right\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"");
   sw.WriteLine("ss:Color=\"#000000\"/>");
   sw.WriteLine("<Border ss:Position=\"Top\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"");
   sw.WriteLine("ss:Color=\"#000000\"/>");
   sw.WriteLine("</Borders>");
   sw.WriteLine("</Style>");
   sw.WriteLine("<Style ss:ID=\"s24\">");
   sw.WriteLine("<Alignment ss:Vertical=\"Bottom\" ss:WrapText=\"1\"/>");
   sw.WriteLine("<Borders>");
   sw.WriteLine("<Border ss:Position=\"Bottom\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"");
   sw.WriteLine("ss:Color=\"#000000\"/>");
   sw.WriteLine("<Border ss:Position=\"Left\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"");
   sw.WriteLine("ss:Color=\"#000000\"/>");
   sw.WriteLine("<Border ss:Position=\"Right\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"");
   sw.WriteLine("ss:Color=\"#000000\"/>");
   sw.WriteLine("<Border ss:Position=\"Top\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"");
   sw.WriteLine("ss:Color=\"#000000\"/>");
   sw.WriteLine("</Borders>");
   sw.WriteLine("<Font ss:Color=\"#FFFFFF\"/>");
   sw.WriteLine("<Interior ss:Color=\"#FF6A6A\" ss:Pattern=\"Solid\"/>");
   //set header colour here
   sw.WriteLine("</Style>");
   sw.WriteLine("</Styles>");
   foreach (DataTable myTable in cList)
   {
      CreateWorkSheet(myTable.TableName, sw, myTable, CellWidth);
   }
   sw.WriteLine("</Workbook>");
   HttpContext.Current.Response.Write(sw.ToString());
   HttpContext.Current.Response.End();
}

/// <summary>
/// Method to create worksheet
/// </summary>
/// <param name="wsName"></param>
/// <param name="sw"></param>
/// <param name="dt"></param>
/// <param name="cellwidth"></param>
private void CreateWorkSheet(string wsName, System.IO.StringWriter sw, DataTable dt, int cellwidth)
{
    if (dt.Columns.Count > 0)
    {
       sw.WriteLine("<Worksheet ss:Name=\"" + wsName + "\">");
       int cCount = dt.Columns.Count;
       long rCount = dt.Rows.Count + 1;
       sw.WriteLine("<Table ss:ExpandedColumnCount=\"" + cCount + "\" ss:ExpandedRowCount=\"" + rCount + "\"x:FullColumns=\"1\"");
       sw.WriteLine("x:FullRows=\"1\">");
       for (int i = (cCount - cCount); i <= (cCount - 1); i++)
       {
          sw.WriteLine("<Column ss:AutoFitWidth=\"1\" ss:Width=\"" + cellwidth + "\"/>");
       }
       DataTableRowIteration(dt, sw);
       sw.WriteLine("</Table>");
       sw.WriteLine("<WorksheetOptions xmlns=\"urn:schemas-microsoft-com:office:excel\">");
       sw.WriteLine("<Selected/>");
       sw.WriteLine("<DoNotDisplayGridlines/>");
       sw.WriteLine("<ProtectObjects>False</ProtectObjects>");
       sw.WriteLine("<ProtectScenarios>False</ProtectScenarios>");
       sw.WriteLine("</WorksheetOptions>");
       sw.WriteLine("</Worksheet>");
   }
}

/// <summary>
/// Method to create rows by iterating thru datatable rows
/// </summary>
/// <param name="dt"></param>
/// <param name="sw"></param>
private void DataTableRowIteration(DataTable dt, System.IO.StringWriter sw)
{
     sw.WriteLine("");
     foreach (DataColumn dc in dt.Columns)
     {
        string tcText = dc.ColumnName;
        sw.WriteLine("<data>" + tcText + "</data>");
     }
     sw.WriteLine("");
     foreach (DataRow dr in dt.Rows)
     {
        sw.WriteLine("");
        foreach (DataColumn tc in dt.Columns)
       {
          string gcText = dr[tc].ToString();
          sw.WriteLine("<data>" + gcText + "</data>");
       }
       sw.WriteLine("");
     }
}

Above code help us to create worksheets as per requirements. You just need to pass the object list [DataTable objects]. So it will iterate thru each one and create worksheets for each of them.

Please make sure you create Datatable with TableName. so that we can create workbook with different worksheet name. I have used Datatable name as worksheet name.

Hope this will help!!!

Jay Ganesh

Add Custom Message Header in WCF 4 Calls

Often, we want to pass some data to some or maybe all our service operations. This data is usually context data such as user tokens, or environmental preferences of the user or machine.

In simple web service we can pass custom header information using Attribute called “[SoapHeaderAttribute (“ServiceHeader”, Direction=SoapHeaderDirection.In)]” along with Web Method signatures.

But in WCF, we cannot use the same attribute.

One way would be to pass it as an additional request parameter. But, each and every method call needs to have this parameter(s) repeatedly. Not a very clean solution. Also, if the data type of this parameter changes, all the method signatures and their calls need to be changed.

A nice and easy way to pass that data is to use Message Headers.

In WCF, to pass the custom header information along with method call, we need to implement custom inspector for client and service which will implement the BeforeSendRequest and AfterRecieveRequest methods to inject the custom header.

In order to do this we need following objects/classes:

  1. Soap Header
  2. Message Inspector
  3. Client Context and Server Context class
  4. Custom Behavior

Let’s start creating these classes one by one.

1) Soap Header:

CustomHeader class is used to create custom header for service in which we want to pass header information along with method call. CustomHeader class contains the information that we want to pass along with method call. You can define the structure as per your needs.


    [DataContract]
    public class ServiceHeader
    {

        [DataMember]
        public string EmployeeID { get; set; }

        [DataMember]
        public string WindowsLogonID { get; set; }

        [DataMember]
        public string KerberosID { get; set; }

        [DataMember]
        public string SiteminderToken { get; set; }

    }

    public class CustomHeader : MessageHeader
    {
        private const string CUSTOM_HEADER_NAME = "HeaderName";
        private const string CUSTOM_HEADER_NAMESPACE = "YourNameSpace";

        private ServiceHeader _customData;

        public ServiceHeader CustomData
        {
            get
            {
                return _customData;
            }
        }

        public CustomHeader()
        {
        }

        public CustomHeader(ServiceHeader customData)
        {
            _customData = customData;
        }

        public override string Name
        {
            get { return (CUSTOM_HEADER_NAME); }
        }

        public override string Namespace
        {
            get { return (CUSTOM_HEADER_NAMESPACE); }
        }

        protected override void OnWriteHeaderContents(System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(ServiceHeader));
            StringWriter textWriter = new StringWriter();
            serializer.Serialize(textWriter, _customData);
            textWriter.Close();

            string text = textWriter.ToString();

            writer.WriteElementString(CUSTOM_HEADER_NAME, "Key", text.Trim());
        }

        public static ServiceHeader ReadHeader(Message request)
        {
            Int32 headerPosition = request.Headers.FindHeader(CUSTOM_HEADER_NAME, CUSTOM_HEADER_NAMESPACE);
            if (headerPosition == -1)
                return null;

            MessageHeaderInfo headerInfo = request.Headers[headerPosition];

            XmlNode[] content = request.Headers.GetHeader<XmlNode[]>(headerPosition);

            string text = content[0].InnerText;

            XmlSerializer deserializer = new XmlSerializer(typeof(ServiceHeader));
            TextReader textReader = new StringReader(text);
            ServiceHeader customData = (ServiceHeader)deserializer.Deserialize(textReader);
            textReader.Close();

            return customData;
        }
    }

As you can see it is a type inheriting from MessageHeader class. Notice the OnWriteHeaderContents override, which is invoked by WCF infrastructure to serialize the SOAP Header, and the ReadHeader static method that we will use later.

2) Message Inspector:

SOAP Header needs to be added by the consumer and read by the service. To do this we need a Message Inspector like the following one:


    /// <summary>
    /// This class is used to inspect the message and headers on the server side,
    /// This class is also used to intercept the message on the client side, before/after any request is made to the server.
    /// </summary>
    public class CustomMessageInspector : IClientMessageInspector, IDispatchMessageInspector
    {
        #region Message Inspector of the Service

        /// <summary>
        /// This method is called on the server when a request is received from the client.
        /// </summary>
        /// <param name="request"></param>
        /// <param name="channel"></param>
        /// <param name="instanceContext"></param>
        /// <returns></returns>
        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            // Create a copy of the original message so that we can mess with it.
            MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
            request = buffer.CreateMessage();
            Message messageCopy = buffer.CreateMessage();

            // Read the custom context data from the headers
            ServiceHeader customData = CustomHeader.ReadHeader(request);

            // Add an extension to the current operation context so that our custom context can be easily accessed anywhere.
            ServerContext customContext = new ServerContext();

            if (customData != null)
            {
                customContext.KerberosID = customData.KerberosID;
                customContext.SiteminderToken = customData.SiteminderToken;
            }
            OperationContext.Current.IncomingMessageProperties.Add("CurrentContext", customContext);
            return null;
        }

        /// <summary>
        /// This method is called after processing a method on the server side and just
        /// before sending the response to the client.
        /// </summary>
        /// <param name="reply"></param>
        /// <param name="correlationState"></param>
        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            // Do some cleanup
            OperationContext.Current.Extensions.Remove(ServerContext.Current);
        }

        #endregion

        #region Message Inspector of the Consumer

        /// <summary>
        /// This method will be called from the client side just before any method is called.
        /// </summary>
        /// <param name="request"></param>
        /// <param name="channel"></param>
        /// <returns></returns>
        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            // Prepare the request message copy to be modified
            MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
            request = buffer.CreateMessage();

            ServiceHeader customData = new ServiceHeader();

            customData.KerberosID = ClientContext.KerberosID;
            customData.SiteminderToken = ClientContext.SiteminderToken;

            CustomHeader header = new CustomHeader(customData);

            // Add the custom header to the request.
            request.Headers.Add(header);

            return null;
        }

        /// <summary>
        /// This method will be called after completion of a request to the server.
        /// </summary>
        /// <param name="reply"></param>
        /// <param name="correlationState"></param>
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {

        }

        #endregion
    }

As you can see from the code sample above, we use the IClientMessageInspector implementation to handle the addition of the header in the consumer-side code, while we use the IDispatchMessageInspector on the service side, to extract the header. It is interesting the FindHeader method of the MessageHeaders collection, as well as the method GetReaderAtHeader, provided by the same collection of Headers. The result of this last method is an XmlDictionaryReader that we use to read our custom header content, through the ReadHeader static method we’ve already introduced.

3) Client Context and Server Context class:

ClientContext class is used to store the header information before calling the method, so when you want to attach the custom header data, you just need to set the values for this ClientContext class. These values get fetched inside BeforeSendRequest method of CustomMessageInspector class and send along with the request made.


    /// <summary>
    /// This class will act as a custom context in the client side to hold the context information.
    /// </summary>
    public class ClientContext
    {
        public static string EmployeeID;
        public static string WindowsLogonID;
        public static string KerberosID;
        public static string SiteminderToken;
    }

At server side, once custom header is received, it will be stored inside this ServerContext class object, so that we can access it anytime once request is received.


    /// <summary>
    /// This class will act as a custom context, an extension to the OperationContext.
    /// This class holds all context information for our application.
    /// </summary>
    public class ServerContext : IExtension<OperationContext>
    {
        public string EmployeeID;
        public string WindowsLogonID;
        public string KerberosID;
        public string SiteminderToken;

        // Get the current one from the extensions that are added to OperationContext.
        public static ServerContext Current
        {
            get
            {
                return OperationContext.Current.Extensions.Find<ServerContext>();
            }
        }

        #region IExtension<OperationContext> Members
        public void Attach(OperationContext owner)
        {
        }

        public void Detach(OperationContext owner)
        {
        }
        #endregion
    }

4) Custom Behavior:

The service will be able to read the Key provided through the custom header simply querying the IncomingMessageProperties dictionary:


OperationContext.Current.IncomingMessageProperties["CurrentContext"];

Of course the Custom Message Inspector needs to be plugged into the WCF pipeline using a custom behavior like the following one.


    /// <summary>
    /// This custom behavior class is used to add both client and server inspectors to
    /// the corresponding WCF end points.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class)]
    public class CustomBehavior : Attribute, IServiceBehavior, IEndpointBehavior
    {
        #region IEndpointBehavior Members

        void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
            CustomMessageInspector inspector = new CustomMessageInspector();
            clientRuntime.MessageInspectors.Add(inspector);
        }

        void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {
            ChannelDispatcher channelDispatcher = endpointDispatcher.ChannelDispatcher;
            if (channelDispatcher != null)
            {
                foreach (EndpointDispatcher ed in channelDispatcher.Endpoints)
                {
                    CustomMessageInspector inspector = new CustomMessageInspector();
                    ed.DispatchRuntime.MessageInspectors.Add(inspector);
                }
            }
        }

        void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { }

        #endregion

        #region IServiceBehavior Members

        void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
        }

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription desc, ServiceHostBase host)
        {
            foreach (ChannelDispatcher cDispatcher in host.ChannelDispatchers)
            {
                foreach (EndpointDispatcher eDispatcher in cDispatcher.Endpoints)
                {
                    eDispatcher.DispatchRuntime.MessageInspectors.Add(new CustomMessageInspector());
                }
            }
        }

        void IServiceBehavior.Validate(ServiceDescription desc, ServiceHostBase host) { }

        #endregion
    }

Implement the IEndpointBehavior interface to modify, examine, or extend some aspect of endpoint-wide execution at the application level for either client or service applications.

  • Use the AddBindingParameters method to pass custom data at runtime to enable bindings to support custom behavior.
  • Use the ApplyClientBehavior method to modify, examine, or insert extensions to an endpoint in a client application.
  • Use the ApplyDispatchBehavior method to modify, examine, or insert extensions to endpoint-wide execution in a service application.
  • Use the Validate method to confirm that a ServiceEndpoint meets specific requirements. This can be used to ensure that an endpoint has a certain configuration setting enabled, supports a particular feature and other requirements.

Implement IServiceBehavior to modify, examine, or extend some aspect of service-wide execution at the application level:

  • Use the ApplyDispatchBehavior method to change run-time property values or insert custom extension objects such as error handlers, message or parameter interceptors, security extensions, and other custom extension objects.
  • Use the Validate method to examine the description before constructs the executing service to confirm that it can execute properly.
  • Use the AddBindingParameters method to pass to a binding element the custom information for the service so that it can support the service correctly.

Adding Behavior to the Runtime

When you construct a ServiceHost or client-side ChannelFactory, the runtime reflects over the service types, reads the configuration file, and starts building an in-memory description of the service. Within ServiceHost, this description is made available to you via the Description property (of type ServiceDescription). Within ChannelFactory, it’s made available via the Endpoint property (of type ServiceEndpoint); the client-side description is limited to the target endpoint.

The ServiceDescription contains a full description of the service and each endpoint (ServiceEndpoint), including contracts (ContractDescription) and operations (OperationDescription). ServiceDescription provides a Behaviors property (a collection of type IServiceBehavior) that models a collection of service behaviors. Each ServiceEndpoint also has a Behaviors property (a collection of type IEndpointBehavior) that models the individual endpoint behaviors. Likewise, ContractDescription and OperationDescription each have an appropriate Behaviors property.

These behavior collections are automatically populated during the ServiceHost and ChannelFactory construction process with any behaviors that are found in your code (via attributes) or within the configuration file (more on this shortly). You can also add behaviors to these collections manually after construction. The following example shows how to add the CustomBehavior to the host as a service behavior:


WCFServiceClient ws = new WCFServiceClient();
ws.ChannelFactory.Endpoint.Behaviors.Add(new CustomBehavior());

Adding Behavior with Attribute

During the ServiceHost/ChannelFactory construction process, the runtime reflects over the service types and configuration file and automatically adds any behaviors it finds to the appropriate behavior collections in the ServiceDescription.


  /// <summary>
    /// Summary description for WCFService
    /// </summary>
    [CustomBehavior]
    public class WCFService : IWCFService
    {
    }

That’s all! Enjoy your custom header passing using behavior specified.

Hope this will help!!!

Jay Ganesh

No more pain to configure WCF 4 services

Developer who has worked with ASP.NET Web Services (ASMX) and WCF always feels that using predecessor was much more easier. It just because WCF configuration is much more complex as compare to ASP.NET Web Service.

WHY ??????

With ASMX, you were able to define a [WebMethod] operation and the runtime automatically provided a default configuration for the underlying communications. When moving to WCF 3.x, on the other hand, developers have to know enough about the various WCF configuration options to define at least one endpoint.

In an effort to make the overall WCF experience just as easy as ASMX, WCF 4 comes with a new “default configuration” model that completely removes the need for any WCF configuration. If you don’t provide any WCF configuration for a particular service, the WCF 4 runtime automatically configures your service with some standard endpoints and default binding/behavior configurations. This makes it much easier to get a WCF service up and running, especially for those who aren’t familiar with the various WCF configuration options.

Let’s discuss some of the standard configuration options that WCF 4 support :

1) Default Endpoints
2) Default Protocol Mapping
3) Default Binding Configurations
4) Default Behavior Configurations

1) Default Endpoints

With WCF 3.X, if you try to host a service without configured endpoints, ServiceHost instance will throw an exception informing you that you need to configure at least one endpoint. With WCF 4, this is no longer the case because the runtime automatically adds one or more ‘default endpoints’ for you.

Now question comes in mind, How this is done by WCF 4?

Answer to Question is like : When Host application calls Open method on ServiceHost instance, it build internal service description from the application configuration file. Than it check the count of configured endpoints. if it is still zero than it will call “AddDefaultEndpoints” public method and method will adds one default endpoint per base address for each service contract implemented by the service.

Clear or Confused ??

Lets take one example to be more clear on it.

If the service implements two service contracts and you configure the host with a single base address, AddDefaultEndpoints will configure the service with two default endpoints (one for each service contract). However, if the service implements two service contracts and the host is configured with two base addresses (one for HTTP and one for TCP), AddDefaultEndpoints will configure the service with four default endpoints.

I Hope now its clear….if still not…please go thru the link provided for more details on it.

-> New Features in WCF 4 that Will Instantly Make You More Productive
(http://www.code-magazine.com/Article.aspx?quickid=1006061)

2) Default Protocol Mapping

In .Net 4.0 framework, default protocol mapping between transport protocol schemes and the built in WCF bindings are as follows :


   <protocolMapping>
      <add scheme="http" binding="basicHttpBinding" bindingConfiguration="" />
      <add scheme="net.tcp" binding="netTcpBinding" bindingConfiguration=""/>
      <add scheme="net.pipe" binding="netNamedPipeBinding" bindingConfiguration=""/>
      <add scheme="net.msmq" binding="netMsmqBinding" bindingConfiguration=""/>
   </protocolMapping>

You can override these mappings at machine level by adding this section to machine.config file and modify the bindings as per your needs.

If you want to override this mappings at application level than you can override the above section in application/web configfile.

3) Default Binding Configurations

In WCF 3.x , binding can be done like this :


<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicBindingMtom" messageEncoding="Mtom"/>
      </basicHttpBinding>
    </bindings>
    <services>
      <service name="HelloService">
        <endpoint address="mtom" binding="basicHttpBinding"
                  bindingConfiguration="BasicBindingMtom"
                  contract="IHello"/>
      </service>
    </services>
  </system.serviceModel>
</configuration>

Here “BasicBindingMtom” binding configuration overrides the defaults for BasicHttpBinding by changing the message encoding to “Mtom”. However this binding will effect only when you apply it to a specific endpoint thru “bindingConfiguration” attribute.

With WCF 4, binding can be done like this :


      <basicHttpBinding>
        <binding messageEncoding="Mtom"/>
      </basicHttpBinding>

No name attribute required. This feature gives you a simple mechanism to define a standard set of binding defaults that you can use across all your services without imposing any complexities of binding configurations.

4) Default Behavior Configurations

With WCF 4, it is possible to define default behavior configurations for services and endpoints.


    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>

Above configuration turns on the metadata for any service that doesn’t come with explicit behavior configuration.

In WCF 4, Behavior configuration also support inheritance model. It means that if application defines a behavior using same name as one already defined in machine.config, the application specific behavior configuration will get merged with machine configuration.

With these new additions in the WCF 4, it will be easier for developers to configure the services. I am sure that many developers will feel relaxed by having these new features in WCF and also start using it. Thats all from my side for this post.

Hope this will help !!!

Jay Ganesh ……

DataContract Vs MessageContract

1. Comparison:

Data Contracts:

WCF data contracts provide a mapping function between .NET CLR types that are defined in code and XML Schemas Definitions defined by the W3C organization (www.w3c.org/) that are used for communication outside the service.

you can say “Data contract is a formal agreement between a service and a client that abstractly describes the data to be exchanged”. That is, to communicate, the client and the service do not have to share the same types, only the same data contracts. A data contract precisely defines, for each parameter or return type, what data is serialized (turned into XML) to be exchanged.

Message Contracts:

Message contracts describe the structure of SOAP messages sent to and from a service and enable you to inspect and control most of the details in the SOAP header and body. Whereas data contracts enable interoperability through the XML Schema Definition (XSD) standard, message contracts enable you to interoperate with any system that communicates through SOAP.

Using message contracts gives you complete control over the SOAP message sent to and from a service by providing access to the SOAP headers and bodies directly. This allows use of simple or complex types to define the exact content of the SOAP parts.

2. Why use MessageContract when DataContract is there?

Data contracts are used to define the data structure. Messages that are simply a .NET type, lets say in form of POCO (plain old CLR object), and generate the XML for the data you want to pass.

Message contracts are preferred only when there is a need to “control” the layout of your message(the SOAP message); for instance, adding specific headers/footer/etc to a message.

sometimes complete control over the structure of a SOAP message is just as important as control over its contents. This is especially true when interoperability is important or to specifically control security issues at the level of the message or message part. In these cases, you can create a message contract that enables you to use a type for a parameter or return value that serializes directly into the precise SOAP message that you need.

3. Why we use MessageContract to pass SOAP headers ?

Passing information in SOAP headers is useful if you want to communicate information “out of band” from the operation signature.

For instance, session or correlation information can be passed in headers, rather than adding additional parameters to operations or adding this information as fields in the data itself.

Another example is security, where you may want to implement a custom security protocol (bypassing WS-Security) and pass credentials or tokens in custom SOAP headers.

A third example, again with security, is signing and encrypting SOAP headers, where you may want to sign and/or encrypt some or all header information. All these cases can be handled with message contracts. The downside with this technique is that the client and service must manually add and retrieve the information from the SOAP header, rather than having the serialization classes associated with data and operation contracts do it for you.

4. Can’t mix datacontracts and messagecontracts.

Because message-based programming and parameter-based programming cannot be mixed, so you cannot specify a DataContract as an input argument to an operation and have it return a MessageContract, or specify a MessageContract as the input argument to an operation and have it return a DataContract. You can mix typed and untyped messages, but not messageContracts and DataContracts. Mixing message and data contracts will cause a runtime error when you generate WSDL from the service.

Hope this will help !!!

@@@ Happy Diwali @@@