Saturday, June 9, 2018

Understanding the query execution plan

Whenever you issue a SQL statement in the SQL Server engine, SQL Server first has to determine the best possible way to execute it. In order to carry this out, the Query Optimizer (a system that generates the optimal query execution plan before executing the query) uses several information like the data distribution statistics, index structure, metadata, and other information to analyze several possible execution plans and finally select one that is likely to be the best execution plan most of the time.

Did you know? You can use SQL Server Management Studio to preview and analyze the estimated execution plan for the query that you are going to issue. After writing the SQL in SQL Server Management Studio, click on the estimated execution plan icon (see below) to see the execution plan before actually executing the query.

(Note: Alternatively, you can switch the actual execution plan option "on" before executing the query. If you do this, Management Studio will include the actual execution plan that is being executed along with the result set in the result window.)

Understanding the query execution plan in detail

Each icon in the execution plan graph represents an action item (Operator) in the plan. The execution plan has to be read from right to left, and each action item has a percentage of cost relative to the total execution cost of the query (100%).

In the above execution plan graph, the first icon in the right most part represents a "Clustered Index Scan" operation (reading all primary key index values in the table) in the HumanResources table (that requires 100% of the total query execution cost), and the left most icon in the graph represents a SELECT operation (that requires only 0% of the total query execution cost).

Following are the important icons and their corresponding operators you are going to see frequently in the graphical query execution plans:


(Each icon in the graphical execution plan represents a particular action item in the query. For a complete list of the icons and their corresponding action items, go to http://technet.microsoft.com/en-us/library/ms175913.aspx.)

Note the "Query cost" in the execution plan given above. It has 100% cost relative to the batch. That means, this particular query has 100% cost among all queries in the batch as there is only one query in the batch. If there were multiple queries simultaneously executed in the query window, each query would have its own percentage of cost (less than 100%).

To know more details for each particular action item in the query plan, move the mouse pointer on each item/icon. You will see a window that looks like the following:

This window provides detailed estimated information about a particular query item in the execution plan. The above window shows the estimated detailed information for the clustered index scan and it looks for the row(s) which have/has Gender = 'M' in the Employee table in HumanResources schema in the AdventureWorks database. The window also shows the estimated IO, CPU, number of rows, with the size of each row, and other costs that is used to compare with other possible execution plans to select the optimal plan.

I found an article that can help you further understand and analyze TSQL execution plans in detail. You can take a look at it here: http://www.simple-talk.com/sql/performance/execution-plan-basics/.

What information do we get by viewing the execution plans?

Whenever any of your query performs slowly, you can view the estimated (and, actual if required) execution plan and can identify the item that is taking the most amount of time (in terms of percentage) in the query. When you start reviewing any TSQL for optimization, most of the time, the first thing you would like to do is view the execution plan. You will most likely quickly identify the area in the SQL that is creating the bottleneck in the overall SQL.

Keep watching for the following costly operators in the execution plan of your query. If you find one of these, you are likely to have problems in your TSQL and you need to re-factor the TSQL to improve performance.

Table Scan: Occurs when the corresponding table does not have a clustered index. Most likely, creating a clustered index or defragmenting index will enable you to get rid of it.

Clustered Index Scan: Sometimes considered equivalent to Table Scan. Takes place when a non-clustered index on an eligible column is not available. Most of the time, creating a non-clustered index will enable you to get rid of it.

Hash Join: The most expensive joining methodology. This takes place when the joining columns between two tables are not indexed. Creating indexes on those columns will enable you to get rid of it.

Nested Loops: Most cases, this happens when a non-clustered index does not include (Cover) a column that is used in the SELECT column list. In this case, for each member in the non-clustered index column, the database server has to seek into the clustered index to retrieve the other column value specified in the SELECT list. Creating a covered index will enable you to get rid of it.

RID Lookup: Takes place when you have a non-clustered index but the same table does not have any clustered index. In this case, the database engine has to look up the actual row using the row ID, which is an expensive operation. Creating a clustered index on the corresponding table would enable you to get rid of it.

Wednesday, June 6, 2018

Improve stored procedure performance in SQL Server (Indexing)

Implement computed columns and create an index on these


You might have written application code where you select a result set from the database and do a calculation for each row in the result set to produce the ultimate information to show in the output. For example, you might have a query that retrieves Order information from the database, and in the application, you might have written code to calculate the total Order price by doing arithmetic operations on Product and Sales data. But, why don't you do all this processing in the database?

Take a look at the following figure. You can specify a database column as a "computed column" by specifying a formula. While your TSQL includes the computed column in the select list, the SQL engine will apply the formula to derive the value for this column. So, while executing the query, the database engine will calculate the Order total price and return the result for the computed column.


Sounds good. Using a computed column in this way would allow you to do the entire calculation in the back-end. But sometimes, this might be expensive if the table contains a large number of rows. The situation might get worse if the computed column is specified in the WHERE clause in a SELECT statement. In this case, to match the specified value in the WHERE clause, the database engine has to calculate the computed column's value for each row in the table. This is a very inefficient process because it always requires a table or full clustered index scan.

So, we need to improve performance on computed columns. How? The solution is, you need to create an index on the computed columns. When an index is built on a computed column, SQL Server calculates the result in advance and builds an index over them. Additionally, when the corresponding column values are updated (that the computed column depends on), the index values on the computed column are also updated. So, while executing the query, the database engine does not have to execute the computation formula for every row in the result set. Rather, the pre-calculated values for the computed column are just selected and returned from the index. As a result, creating an index on a computed column gives you excellent performance boost.

Note: If you want to create an index on a computed column, you must make sure that the computed column formula does not contain any "nondeterministic" function (for example, getdate() is a nondeterministic function because each time you call it, it returns a different value).

Create "Indexed Views"

Did you know that you can create indexes on views (with some restrictions)? Well, if you have come this far, let us learn about indexed views!

Why do we use Views?

As we all know, Views are nothing but compiled SELECT statements residing as objects in a database. If you implement your common and expensive TSQLs using Views, it's obvious that you can re-use these across your data access routines. Doing this will enable you to join Views with other tables/views to produce an output result set, and the database engine will merge the view definition with the SQL you provide and will generate an execution plan to execute. Thus, sometimes Views allow you to re-use common complex SELECT queries across your data access routines, and also let the database engine to re-use execution plans for some portion of your TSQLs.

Take my word. Views don't give you any significant performance benefit. In my early SQL days, when I first learned about views, I got exited thinking that Views were something that "remembers" the result for the complex SELECT query it is built upon. But soon, I was disappointed to know that Views are nothing but compiled queries, and Views just can't remember any result set. (Poor me! I can bet many of you got the same wrong idea about Views in your first SQL days.)

But now, I may have a surprise for you! You can do something on a View so that it can truly "remember" the result set for the SELECT query it is composesd of. How? It's not hard; you just have to create indexes on the View.

Well, if you apply indexing on a View, the View becomes an "indexed view". For an indexed View, the database engine processes the SQL and stores the result in the data file just like a clustered table. SQL Server automatically maintains the index when data in the base table changes. So, when you issue a SELECT query on the indexed View, the database engine simply selects values from an index, which obviously performs very fast. Thus, creating indexes on views gives you excellent performance benefits.

Please note that nothing comes free. As creating indexed Views gives you performance boost, when data in the base table changes, the database engine has to update the index also. So, you should consider creating indexed Views when the view has to process too many rows with aggregate functions, and when data and the base table do not change often.

How to create an indexed View?

Create/modify the view specifying the SCHEMABINDING option:
CREATE VIEW dbo.vOrderDetails
WITH SCHEMABINDING
AS
  SELECT...
Create a unique clustered index on the View.
Create a non-clustered index on the View as required.
Wait! Don't get too much exited about indexed Views. You can't always create indexes on Views. Following are the restrictions:

The View has to be created with the SCHEMABINDING option. In this case, the database engine will not allow you to change the underlying table schema.
The View cannot contain nondeterministic functions, DISTINCT clause, or subquery.
The underlying tables in the View must have a clustered index (primary keys).
Try finding the expensive TSQLs in your application that are already implemented using Views or that could be implemented using Views. Try creating indexes on these Views to boost up your data access performance.

Create indexes on User Defined Functions (UDF)

Did you know this? You can create indexes on User Defined Functions too in SQL Server. But, you can't do this in a straightforward way. To create an index on a UDF, you have to create a computed column specifying a UDF as the formula, and then you have to create an index on the computed column field.

Here are the steps to follow:
Create the function (if not exists already) and make sure that the function (that you want to create the index on) is deterministic. Add the SCHEMABINDING option in the function definition and make sure that there is no non-deterministic function/operator (getdate() or distinct etc.) in the function definition.
For example:
CREATE FUNCTION [dbo.ufnGetLineTotal]
(
-- Add the parameters for the function here
@UnitPrice [money],
@UnitPriceDiscount [money],
@OrderQty [smallint]
)
RETURNS money
WITH SCHEMABINDING
AS
BEGIN
    return (((@UnitPrice*((1.0)-@UnitPriceDiscount))*@OrderQty))
END
Add a computed column in your desired table and specify the function with parameters as the value of the computed column.
Hide   Copy Code
CREATE FUNCTION [dbo.ufnGetLineTotal]
(
-- Add the parameters for the function here
@UnitPrice [money],
@UnitPriceDiscount [money],
@OrderQty [smallint]
)
RETURNS money
WITH SCHEMABINDING
AS
BEGIN
    return (((@UnitPrice*((1.0)-@UnitPriceDiscount))*@OrderQty))
END

Specifying UDF as computation formula for the computed column
Create an index on the computed column.
We have already seen that we can create an index on computed columns to retrieve faster results on computed columns. But, what benefit could we achieve by using a UDF in the computed columns and creating an index on those?

Well, doing this would give you a tremendous performance benefit when you include the UDF in a query, especially if you use UDFs in the join conditions between different tables/views. I have seen lots of join queries written using UDFs in the joining conditions. I've always thought UDFs in join conditions are bound to be slow (if the number of results to process is significantly large), and there has to be a way to optimize it. Creating indexes on functions in the computed columns is the solution.

Create indexes on XML columns

Create indexes on XML columns if there is any. XML columns are stored as binary large objects (BLOBs) in SQL Server (SQL Server 2005 and later) which can be queried using XQuery, but querying XML data types can be very time consuming without an index. This is true especially for large XML instances because SQL Server has to shred the binary large object containing the XML at runtime to evaluate the query.

To improve query performance on XML data types, XML columns can be indexed. XML indexes fall in two categories:

Primary XML indexes

When the primary index on an XML column is created, SQL Server shreds the XML content and creates several rows of data that includes information like element and attribute names, the path to the root, node types and values, and so on. So, creating the primary index enables SQL server to support XQuery requests more easily.

Following is the syntax for creating a primary XML index:

CREATE PRIMARY XML INDEX
index_name
ON <object> ( xml_column )  
Secondary XML indexes
Creating primary XML indexes improves XQuery performance because the XML data is shredded already. But, SQL Server still needs to scan through the shredded data to find the desired result. To further improve query performance, secondary XML index should be created on top of primary XML indexes.

Three types of secondary XML indexes are there. These are:

"Path" secondary XML indexes: Useful when using the .exist() methods to determine whether a specific path exists.
"Value" secondary XML indexes: Used when performing value-based queries where the full path is unknown or includes wildcards.
"Property" secondary XML indexes: Used to retrieve property values when the path to the value is known.
Following is the syntax for creating secondary XML indexes:

CREATE XML INDEX
index_name
ON <object> ( xml_column )
USING XML INDEX primary_xml_index_name
FOR { VALUE | PATH | PROPERTY }
Please note that the above guidelines are the basics. But, creating indexes blindly on each and every table on the mentioned columns may not always result in performance optimization, because sometimes, you may find that creating indexes on particular columns in particular tables result in slowing down data insert/update operations in that table (particularly if the table has a low selectivity on a column). Also, if the table is a small one containing a small number of rows (say, <500), creating an index on the table might in turn increase the data retrieval performance (because, for smaller tables, a table scan is faster). So, we should be judicious while determining the columns to create indexes on.

Top 5 Ways to Find Slow Queries (Performance Tuning)

Top 5 Ways to Find Slow Queries (Performance Tuning)
Provide some tips for how developers can find slow SQL queries and do performance tuning in SQL Server.

Find Slow Queries With SQL DMVs


One of the great features of SQL Server is all of the dynamic management views (DMVs) that are built into it. There are dozens of them and they can provide a wealth of information about a wide range of topics.

There are several DMVs that provide data about query stats, execution plans, recent queries and much more. These can be used together to provide some amazing insights.

For example, this query below can be used to find the queries that use the most reads, writes, worker time (CPU), etc.

SELECT TOP 10 SUBSTRING(qt.TEXT, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(qt.TEXT)
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2)+1),
qs.execution_count,
qs.total_logical_reads, qs.last_logical_reads,
qs.total_logical_writes, qs.last_logical_writes,
qs.total_worker_time,
qs.last_worker_time,
qs.total_elapsed_time/1000000 total_elapsed_time_in_S,
qs.last_elapsed_time/1000000 last_elapsed_time_in_S,
qs.last_execution_time,
qp.query_plan
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
ORDER BY qs.total_logical_reads DESC -- logical reads
-- ORDER BY qs.total_logical_writes DESC -- logical writes
-- ORDER BY qs.total_worker_time DESC -- CPU time
The result of the query will look something like this below. The image below is from a marketing app I made. You can see that one particular query (the top one) takes up all the resources.

By looking at this, I can copy that SQL query and see if there is some way to improve it, add an index, etc.
Find slow SQL queries with DMVs


  • Pros: Always available basic rollup statistics.
  • Cons: Doesn’t tell you what is calling the queries. Can’t visualize when the queries are being called over time.

Query Reporting via APM Solutions


SQL Server Profiler (DEPRECATED!)


The SQL Server Profiler has been around for a very long time. It is very useful if you are trying to see in real time what SQL queries are being executed against your database.

NOTE: Microsoft has announced that SQL Server Profiler is being deprecated!

SQL Profiler captures very detailed events about your interaction with SQL Server.


  • Login connections, disconnections, and failures
  • SELECT, INSERT, UPDATE, and DELETE statements
  • RPC batch status calls
  • Start and end of stored procedures
  • Start and end of statements within a stored procedure
  • Staart and end of a SQL batch
  • Errors written to the SQL Server error log
  • A lock acquired or released on a database object
  • An opened cursor
  • Security permission checks


SQL Server Extended Events


SQL Azure Query Performance Insights



Summary

Improve stored procedure performance in SQL Server (T-SQL Best Practices)

Tips and optimization to improve stored procedure performance.

Use SET NOCOUNT ON, NO LOCK, Avoid use SELECT *


When performing DML operations (i.e. INSERT, DELETE, SELECT, and UPDATE), SQL Server always returns the number of rows affected. In complex queries with a lot of joins, this becomes a huge performance issue. Using SET NOCOUNT ON will improve performance because it will not count the number of rows affected.

Use WITH(NOLOCK) will improve the performance of the select query or use JOIN

Use Database.Schema


It helps SQL Server to find the object.
A fully qualified object name is database.schema.object. When stored procedure is called as schema.object, SQL Server can swiftly find the compiled plan instead of looking for procedure in other schemas when schema is not specified. This may not be a great boost to the performance but should be followed as best practice. All objects inside procedure should also be referred as database.schema.object.

Use JOIN, avoid subqueries or nested queries


Using JOIN is better for the performance than using subqueries or nested queries.

Using IF EXISTS AND SELECT


IF EXISTS is used to check existence of a record, object etc..
And is a handy statement to improve performance of queries where in one only wants to check existence of a record in a table instead of using that record/row in the query.
When doing so use IF EXISTS(SELECT 1 from table) instead of IF EXISTS(Select * from table) as only thing we are interested in is to check the presence of record/s.
So, if the query return 1 then record is present else it’s not. It’s needless to return all column values.

Use set based queries wherever possible.


T-SQL is a set based language and thus loops don’t work well in here. Cursors and while loop are to be used only when a set based query is either expensive or can’t be formulated.

Nullable Columns


Do not use NOT IN when comparing with nullable columns. Use NOT EXISTS instead.
When NOT IN is used in the query (even if the query doesn’t return rows with null values), SQL Server will check each result to see if it is null or not. Using NOT EXISTS will not do the comparison with nulls.

Avoid begin stored procedure’s name with sp_


When the stored procedure is named sp_ or SP_, SQL Server always checks in the system/master database even if the Owner/Schema name is provided. Providing a name without SP_  to a stored procedure avoids this unnecessary check in the system/master database in SQL Server.

Avoid use GROUP BY, ORDER BY, and DISTINCT


Avoid using GROUP BY, ORDER BY, and DISTINCT as much as possible

When using GROUP BY, ORDER BY, or DISTINCT, SQL Server engine creates a work table and puts the data on the work table. After that, it organizes this data in work table as requested by the query, and then it returns the final result.

Use GROUP BY, ORDER BY, or DISTINCT in your query only when absolutely necessary.

Avoid use the COUNT() aggregate in a subquery


Do not use:
SELECT column_list FROM table WHERE 0 < (SELECT count(*) FROM table2 WHERE ..)
Instead, use:
SELECT column_list FROM table WHERE EXISTS (SELECT * FROM table2 WHERE ...)
  • When you use COUNT(), SQL Server does not know that you are doing an existence check. It counts all matching values, either by doing a table scan or by scanning the smallest non-clustered index.
  • When you use EXISTS, SQL Server knows you are doing an existence check. When it finds the first matching value, it returns TRUE and stops looking. The same applies to using COUNT() instead of IN or ANY.

Avoid joining between two types of columns


When joining between two columns of different data types, one of the columns must be converted to the type of the other. The column whose type is lower is the one that is converted.
If you are joining tables with incompatible types, one of them can use an index, but the query optimizer cannot choose an index on the column that it converts. For example:

SELECT column_list FROM small_table, large_table WHERE
smalltable.float_column = large_table.int_column 
In this case, SQL Server converts the integer column to float, because int is lower in the hierarchy than float. It cannot use an index on large_table.int_column, although it can use an index on smalltable.float_column.

Use FULL-TEXT SEARCH


Write full-text queries by using the predicates CONTAINS and FREETEXT and the rowset-valued functions CONTAINSTABLE and FREETEXTTABLE with a SELECT statement.
  • To match words and phrases, use CONTAINS and CONTAINSTABLE.
  • To match the meaning, but not the exact wording, use FREETEXT and FREETEXTTABLE.
Full text searches always outperform LIKE searches.
  • Full text searches will enable you to implement complex search criteria that can't be implemented using a LIKE search, such as searching on a single word or phrase (and optionally, ranking the result set), searching on a word or phrase close to another word or phrase, or searching on synonymous forms of a specific word.
  • Implementing full text search is easier to implement than LIKE search (especially in the case of complex search requirements).
  • For more info on full text search, see http://msdn.microsoft.com/en-us/library/ms142571(SQL.90).aspx

Table Variables and Joins


Temporary tables usually increase a query’s complexity. It’s suggested to avoid the temporary tables.
Do not use table variables in joins. Use temporary tables, CTEs (Common Table Expressions), or derived tables in joins instead.

Even though table variables are very fast and efficient in a lot of situations, the SQL Server engine sees it as a single row. Due to this, they perform horribly when used in joins. CTEs and derived tables perform better with joins compared to table variables.

Try to use UNION to implement an "OR" operation


  • Try not to use "OR" in a query. Instead use "UNION" to combine the result set of two distinguished queries. This will improve query performance.
  • Better use UNION ALL if a distinguished result is not required. UNION ALL is faster than UNION as it does not have to sort the result set to find out the distinguished values.


Use sp_executesql instead of Execute for dynamic queries


The sp_executesql allows for cache plan reuse and protects from SQL Injection. Let’s see an example of the plan reuse.

DBCC FREEPROCCACHE
GO
Declare
@dynamic_sql varchar(max), @salesorderid int
SET @salesorderid=43660
SET @dynamic_sql=' SELECT * FROM Sales.SalesOrderDetail where SalesOrderID='
+ CAST(@salesorderid AS VARCHAR(100)) 
EXECUTE(@dynamic_sql)
The above query executes a dynamic query using EXECUTE command for two values of salesorderid 43660 and 43661. Let’s analyze the cached plans.


As shown in above snapshot, there are two separate plans for the two salesorderids. Let’s now execute the same query with sp_execute SQL and analyze the cached plans.
DECLARE @dynamic_sql NVARCHAR(100)
SET @dynamic_sql = N'SELECT * FROM Sales.SalesOrderDetail where SalesOrderID=@salesorderid'
EXECUTE sp_executesql @dynamic_sql, N'@salesorderid int', @salesorderid = 43661
The above query uses sp_executesql to execute the dynamic query for 2 different values of salesorderid. Let’s analyze the cached plans.

As shown in above snapshot, only one plan is cached and is used for different values of salesorderid.

Unless really required, avoid the use of dynamic SQL because:

  • Dynamic SQL is hard to debug and troubleshoot.
  • If the user provides the input to the dynamic SQL, then there is possibility of SQL injection attacks.

Keep transaction short and crisp


The longer the transaction the longer the locks will be held based on isolation level. This may result in deadlocks and blocking. Open a new query window and execute the below query
use AdventureWorks2014
GO
BEGIN TRANSACTION
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
SELECT * FROM Sales.SalesOrderDetail
Note the session id for the query. Open a new query window and execute the below query. Note down the session id of the query.
begin tran
Update Sales.SalesOrderDetail
SET OrderQty=50 WHERE SalesOrderDetailID=1

The above update query will wait on the select query on shared lock. Let’s analyze the locks for these two sessions.

As shown in above snapshot, session 58 the update query is waiting on shared lock taken by session 57.

Wednesday, May 30, 2018

Customize WCF Envelope and Namespace Prefix



Introduction


WCF allows you to customize the response using DataContract, MessageContract or XmlSerializer. With DataContract you have access only to the body of the message while MessageContract will let you control the headers of the message too. However, you don't have any control over namespace prefixes or on the soap envelope tag from the response message.

When generating the response, WCF uses some default namespaces prefixes and we cannot control this from configuration. For example, <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">. Normally, the prefix should not be a problem according to SOAP standards. However, there are times when we need to support old clients who manually parse the response and they expect fixed format or look for specific prefixes.

Real world example


Recently, I was asked to rewrite an old service using .NET and WCF, keeping compatibility with existing clients. The response from WCF looked like this:

<s:envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
 <s:body>
 <getcardinforesponse xmlns="https://vanacosmin.ro/WebService/soap/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
 <control_area>
 <source>OVF</source>
 <ns1:source_send_date>2014-01-06T14:15:37.1505943+01:00</ns1:source_send_date>
 <api_key />
 <message_id>27970411614463393270</message_id>
 <correlation_id>1</correlation_id>
 </control_area>
 <chip_uid>1111</chip_uid>
 <tls_engraved_id>************1111</tls_engraved_id>
 <reference_id />
 <is_blocked>false</is_blocked>
 <is_useable>false</is_useable>
 <registration_date>2013-12-13T13:06:39.75</registration_date>
 <last_modification>2013-12-20T15:48:52.307</last_modification>
 </getcardinforesponse>
 </s:body>
</s:envelope>
The expected response for existing clients was:

<soap-env:envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://vanacosmin.ro/WebService/soap/">
 <soap-env:body>
 <ns1:getcardinforesponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
 <ns1:control_area>
 <ns1:source>OVF</ns1:source>
 <ns1:source_send_date>2014-01-06T14:15:37.1505943+01:00</ns1:source_send_date>
 <ns1:api_key />
 <ns1:message_id>27970411614463393270</ns1:message_id>
 <ns1:correlation_id>1</ns1:correlation_id>
 </ns1:control_area>
 <ns1:chip_uid>1111</ns1:chip_uid>
 <ns1:tls_engraved_id>************1111</ns1:tls_engraved_id>
 <ns1:reference_id />
 <ns1:is_blocked>false</ns1:is_blocked>
 <ns1:is_useable>false</ns1:is_useable>
 <ns1:registration_date>2013-12-13T13:06:39.75</ns1:registration_date>
 <ns1:last_modification>2013-12-20T15:48:52.307</ns1:last_modification>
 </ns1:getcardinforesponse>
 </soap-env:body>
</soap-env:envelope>
The two messages are equivalent from SOAP or XML perspective. What I needed to do in order to obtain the expected result was:


  • Change the namespace prefix for SOAP envelope
  • Add another namespace with the prefix ns1 in the SOAP envelope
  • Remove the namespace from s:body (because it was moved on a top level)

Possible solutions


There are multiple extension points where the message can be altered

Custom MessageEncoder


You can alter the xml output using a MessageEncoder. A good example about this can be found here.

This approach has several disadvantages, as the author also pointed out in the end of the article:

The message encoder is activated late in the WCF pipeline. If you have message security, a hash is already included in the message. Changing the message will invalidate the hash.
You can build a custom channel so that the changing of the message takes place before the hash is calculated. This is complicated, and the next drawback applies.
If you are using a message inspector (e.g. for logging), you will log the message in its initial state, and not in the form sent to the customer.
If you make big changes to the message, your wsdl contract will not work, so you need to do additional work for metadata exchange, if you want your service to be consumed by new clients without manually parsing your message.

Custom MessageFormatter and a derived Message class


The MessageFormatter is used to transform the result of your method into an instance of Message class. This instance is then passed in the WCF pipeline (message inspectors, channels, encoders). This is the right place to transform your message because all the other extension points will work with the exact same message that you are sending to your clients

The following diagram shows how the message is sent accross different layers in WCF pipeline. You can see that the MessageFormatter is just before the MessageInspector when you send a message from server to client, while the MessageEncoder is a side component which is activated right before the transport layer.

WCF Extension Points

Additional information about the diagram can be found here


Creating a custom MessageFormatter


First, you need to create a IDispatchMessageFormatter class. This is the message formatter. The SerializeReply method will return an instance of your custom Message class.

public class MyCustomMessageFormatter : IDispatchMessageFormatter
{
 private readonly IDispatchMessageFormatter formatter;
 public MyCustomMessageFormatter(IDispatchMessageFormatter formatter)
 {
 this.formatter = formatter;
 }
 public void DeserializeRequest(Message message, object[] parameters)
 {
 this.formatter.DeserializeRequest(message, parameters);
 }
 public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
 {
 var message = this.formatter.SerializeReply(messageVersion, parameters, result);
 return new MyCustomMessage(message);
 }
}

Inherit from Message class


Custom message class

This is the class that will allow you to alter the output of your service.

public class MyCustomMessage : Message
{
 private readonly Message message;
 public MyCustomMessage(Message message)
 {
 this.message = message;
 }
 public override MessageHeaders Headers
 {
 get { return this.message.Headers; }
 }
 public override MessageProperties Properties
 {
 get { return this.message.Properties; }
 }
 public override MessageVersion Version
 {
 get { return this.message.Version; }
 }
 protected override void OnWriteStartBody(XmlDictionaryWriter writer)
 {
 writer.WriteStartElement("Body", "http://schemas.xmlsoap.org/soap/envelope/");
 }
 protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
 {
 this.message.WriteBodyContents(writer);
 }
 protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer)
 {
 writer.WriteStartElement("SOAP-ENV", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
 writer.WriteAttributeString("xmlns", "ns1", null, "https://vanacosmin.ro/WebService/soap/");
 }
}

Custom message class explained

The derived Message class has several overrides that you can use to obtain the required XML:


  • All the methods will use the same XmlDictionaryWriter. This means that if you add a namespace with a prefix, that prefix will be used for the next elements that you add in the same namespace.
  • OnWriteStartEnvelope Method is called first. By default this method will write the s prefix and will add namespaces according to the soap version. I use this method to change the prefix to "SOAP-ENV" and also to add the ns1 namespace.
  • OnWriteStartBody is called second. By default, this method will still use the prefix s for body. This is why I had to override it and write the Body element using the XmlDictionaryWriter.
  • OnWriteBodyContents is called last in this sequence. By calling WriteBodyContents on the original message, I will get the expected result because I have declared the namespace ns1 at the top level
  • There are other methods that you can override if you need more flexibility.


Activate the MessageFormatter


To activate the MessageFormatter we will create an OperationBehavior attribute that must be applied to the methods (on the interface) that we want to use this MessageFormatter.

[AttributeUsage(AttributeTargets.Method)]
public class MobilityProviderFormatMessageAttribute : Attribute, IOperationBehavior
{
 public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }
 public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { }
 public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
 {
 var serializerBehavior = operationDescription.Behaviors.Find<datacontractserializeroperationbehavior>();
 if (dispatchOperation.Formatter == null)
 {
 ((IOperationBehavior)serializerBehavior).ApplyDispatchBehavior(operationDescription, dispatchOperation);
 }
 IDispatchMessageFormatter innerDispatchFormatter = dispatchOperation.Formatter;
 dispatchOperation.Formatter = new MobilityProviderMessageFormatter(innerDispatchFormatter);
 }
 public void Validate(OperationDescription operationDescription) { }
}

Custom Message Formatting in WCF - Namespaces to the SOAP Envelope


I've been working on a WCF client implementation that is calling into a rather peculiar service that is unable to handle messages sent from a WCF SOAP client. In particular the service I needed to call does not allow inline namespaces in the SOAP message and requires that all namespaces are declared with prefixes and declared on the SOAP:Envelope element. WCF by default doesn't work that way.

It felt like I entered into the real bizarro world of a service that was refusing valid XML messages because the messages were formatted a certain way. Specifically the service refuses to read inline namespace declaration and expects all the namespaces to be declared up front on the SOAP envelope. Yeah, you heard that right – send valid XML with validating namespace definitions, but which happen to be defined inline of the body rather than at the top of the envelope and the service fails with a hard exception on the backend.

Hmmm alrighty then… After a lot of back and forth with the provider it comes out that, yes "that's a known issue" and it will be fixed – sometime in the future to which I mentally added "presumably in the next 10 years". Not bloody likely that they are going to help me on the server side.

So since I wasn't going to get any help from the provider, I did what any good developer would do – search StackOverflow and the Web for a solution. Apparently this is not the most bizarre thing ever, as I assumed. A lot of Java/IBM based service apparently have this problem, but even so WCF solutions for this seem to be scarce. I even posted my own StackOverflow question, which I eventually answered myself with what I'm describing here in more detail.

Defining the Problem


To demonstrate what I'm talking about,here's a simple request that WCF natively creates when calling the service:

To demonstrate what I'm talking about,here's a simple request that WCF natively creates when calling the service:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <h:Security>...</h:Security>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <cancelShipmentRequest xmlns="http://www.royalmailgroup.com/api/ship/V2">
      <integrationHeader>
        <dateTime xmlns="http://www.royalmailgroup.com/integration/core/V1">2016-04-02T01:39:06.1735839Z</dateTime>
        <version xmlns="http://www.royalmailgroup.com/integration/core/V1">2</version>
        <identification xmlns="http://www.royalmailgroup.com/integration/core/V1">
          <applicationId>RMG-API-G-01</applicationId>
          <transactionId>vw7nj5jcmtkc</transactionId>
        </identification>
      </integrationHeader>
      <cancelShipments>
        <shipmentNumber>TTT001908905GB</shipmentNumber>
      </cancelShipments>
    </cancelShipmentRequest>
  </s:Body>
</s:Envelope>
This is pretty typical of WCF messages which include a bunch of inline and duplicated namespace declarations. Some of them inherit down (like on cancelShipmentRequest) and others are defined on the actual nodes and repeated (all of the v1 namespaces basically). I'm not quite sure why WCF creates such verbosity in its messages rather than defining namespaces at the top since it is a lot cleaner, but regardless, what's generated matches the schema requirements of the WSDL and in theory the XML sent should work just fine.

But - as pointed out, the provider does not accept inline namespace declarations in the body, so no matter what I tried the requests were getting kicked back with 500 errors from the server. As you might expect, it a look a lot of trial and error and head beating to figure out that the namespaces were the problem. After confirming with the provider this is indeed the problem, a known problem with no workaround, on the server side and it became clear that the only way to get the service to work is to fix it on the client in the WCF Proxy.

Customizing the XML Message using a MessageFormatter
After a lot of digging (and a comment on my StackOverflow question that referenced this blog post) the solution was to implement a custom MessageFormatter in WCF. MessageFormatters sit inside of the WCF pipeline and get fired after a document has been created but before the message has been processed. This gives a chance to hook into various creation events and modify the message as its being created. Essentially I need to hook into the Envelope element creation and then add the namespaces and when creating a subclassed message object there is a way to hook into the Envelope generation event and at that time it's possible to inject the namespaces. And it turns out that this approach works – the namespaces get generated at the Envelope level in the SOAP document.

Creating a custom Message Object with WCF

The key element that has to be modified to handle the Envelope XML creation is the Message object which includes a OnWriteStartElement() method that can be overridden and where the namespaces can be added. But as usually is the case with WCF to get your custom class hooked you have to create several additional classes to get the pipeline to fire your custom handlers.

Leave it to WCF to make this process an exercise in composition hell.  In order to customize the message, three classes are needed:

Message Class
ClientMessageFormatter Class
FormatMessageAttribute Class
You then attach the attribute to each of the Operations in the Service contract interface to get the formatting applied. Most of the code in these classes is just default implementations with a couple of small places where you actually override the default behavior. For the most part this is implement and interface and change the one method that you're interested in.

So here we go – I'll start with the lowest level class that has the implementation and work my way up the stack.

Message Class
First  is the message class implementation which has the actual code that adds the namespaces needed.

 public class RoyalMailCustomMessage : Message
    {
        private readonly Message message;
        public RoyalMailCustomMessage(Message message)
        {
            this.message = message;
        }
        public override MessageHeaders Headers
        {
            get { return this.message.Headers; }
        }
        public override MessageProperties Properties
        {
            get { return this.message.Properties; }
        }
        public override MessageVersion Version
        {
            get { return this.message.Version; }
        }
        protected override void OnWriteStartBody(XmlDictionaryWriter writer)
        {
            writer.WriteStartElement("Body", "http://schemas.xmlsoap.org/soap/envelope/");
        }
        protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
        {
            this.message.WriteBodyContents(writer);
        }
        protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer)
        {
            writer.WriteStartElement("soapenv", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
            writer.WriteAttributeString("xmlns", "oas", null, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
            writer.WriteAttributeString("xmlns", "v2", null, "http://www.royalmailgroup.com/api/ship/V2");
            writer.WriteAttributeString("xmlns", "v1", null, "http://www.royalmailgroup.com/integration/core/V1");
            writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");
            writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");         
        }
    }
The key method is OnWriteStartEnvelope() which receives an XmlWriter that you can use to explicitly create the header element. As you can see I add all the namespaces I need in the header here.

Note that you may have to have multiple message classes if various methods use different namespaces. Lucky for me the service I'm dealing with has only a couple of namespaces that are used for all the service methods so a single overload was all we needed for the methods we called on the service.

ClientMessageFormatter

WCF has two kinds of MessageFormatters: ClientMessageFormatter used for client proxy requests and DispatchMessageFormatter which is used for generating server side messages. So if you need to create custom messages for services use a DispatchMessageFormatter which has slightly different overloads than the ones shown here.

Here's the implementation of the ClientMessageFormatter I used:This code basically overrides the SerializeRequest() method and serializes the new message object we created that includes the overridden namespace inclusions.

public class RoyalMailMessageFormatter : IClientMessageFormatter
{
    private readonly IClientMessageFormatter formatter;
    public RoyalMailMessageFormatter(IClientMessageFormatter formatter)
    {
        this.formatter = formatter;
    }
    public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
    {
        var message = this.formatter.SerializeRequest(messageVersion, parameters);
        return new RoyalMailCustomMessage(message);
    }
    public object DeserializeReply(Message message, object[] parameters)
    {
        return this.formatter.DeserializeReply(message, parameters);
    }
}

FormatMessageAttribute


Finally we also need an attribute to hook up the new formatter to the actual service, which is done by attaching an attribute to the Service Operation on the contract. First you implement the attribute to attach the Formatter.

[AttributeUsage(AttributeTargets.Method)]
public class RoyalMailFormatMessageAttribute : Attribute, IOperationBehavior
{
    public void AddBindingParameters(OperationDescription operationDescription,
        BindingParameterCollection bindingParameters)
    { }
    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
    {
        var serializerBehavior = operationDescription.Behaviors.Find<XmlSerializerOperationBehavior>();
        if (clientOperation.Formatter == null)
            ((IOperationBehavior)serializerBehavior).ApplyClientBehavior(operationDescription, clientOperation);
       
        IClientMessageFormatter innerClientFormatter = clientOperation.Formatter;
        clientOperation.Formatter = new RoyalMailMessageFormatter(innerClientFormatter);
    }
    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    { }
    public void Validate(OperationDescription operationDescription) { }
}
Here I hook up the custom formatter to the operation.

Note that this service uses XmlSerializer messages and because of that I look for the XmlSerializerOperationBehavior to find the behavior. In other cases you may have to use DataContractSerializerBehavior so be sure to step through the code to see which serializer is registered for the service/operation.

That's a lot of freaking ceremony for essentially 10 lines of code that actually do what we need it to do – but at the same time it's pretty amazing that you get that level of control to hook into the process at such a low level. WCF never fails to shock and awe at the same time :-)

Hooking up the Attribute to Service Operations
When all of the ceremony is done the last thing left to do is to attach the behaviors to the operation contracts. In my case I'm using a WCF generated proxy so I do this in the generated Reference.cs file in the ServiceReferences folder (with show all files enabled):

[System.ServiceModel.OperationContractAttribute(Action="cancelShipment", ReplyAction="*")]
[System.ServiceModel.FaultContractAttribute(typeof(MarvelPress.Workflow.Business.RoyalShippingApi.exceptionDetails),
   Action="cancelShipment", Name="exceptionDetails")]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(contactMechanism))]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(baseRequest))]
[RoyalMailFormatMessage]
cancelShipmentResponse1 cancelShipment(MarvelPress.Workflow.Business.RoyalShippingApi.cancelShipmentRequest1 request);
And that's it – I'm now able to run my cancelShipment call against the service and make the request go through.

The new output generated includes all the namespaces at the top of the document (except for the SoapHeader which is generated separately and apparently *can* contain embedded namespaces):

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:v1="http://www.royalmailgroup.com/integration/core/V1"
                  xmlns:v2="http://www.royalmailgroup.com/api/ship/V2"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <s:Header xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <h:Security>
    </h:Security>
  </s:Header>
  <soapenv:Body>
    <v2:cancelShipmentRequest>
      <v2:integrationHeader>
        <v1:dateTime>2016-04-02T07:54:34.9402872Z</v1:dateTime>
        <v1:version>2</v1:version>
        <v1:identification>
          <v1:applicationId>RMG-API-G-01</v1:applicationId>
          <v1:transactionId>he3q6qmer3tv</v1:transactionId>
        </v1:identification>
      </v2:integrationHeader>
      <v2:cancelShipments>
        <v2:shipmentNumber>TTT001908905GB</v2:shipmentNumber>
      </v2:cancelShipments>
    </v2:cancelShipmentRequest>
  </soapenv:Body>
</soapenv:Envelope>
And that puts me back in business. Yay!

A generic [EnvelopeNamespaces] Operation Attribute
The implementation above is specific to the service I was connecting to, but I figured it would be nice to make this actually a bit more generic, by letting you configure the namespaces you want to provide for each method. This will make it easier to work with service that might have different namespace requirements for different messages. So rather than having hard coded namespaces in the Message implementation, it would be nice to actually pass an array of namespace strings.

To do this I created a new set of classes that generically allow an array of strings to be attached.

The result is the [EnvelopeNamespaces] Attribute which you can use by explicitly adding namespaces like this using delimited strings in an array:

// CODEGEN: Generating message contract since the operation cancelShipment is neither RPC nor document wrapped.
[System.ServiceModel.OperationContractAttribute(Action="cancelShipment", ReplyAction="*")]
[System.ServiceModel.FaultContractAttribute(typeof(MarvelPress.Workflow.Business.RoyalShippingApi.exceptionDetails), Action="cancelShipment", Name="exceptionDetails")]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(contactMechanism))]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(baseRequest))]
[EnvelopeNamespaces(EnvelopeNamespaces = new string[] {
    "v1:http://www.royalmailgroup.com/integration/core/V1",
    "v2:http://www.royalmailgroup.com/api/ship/V2",
    "xsi:http://www.w3.org/2001/XMLSchema-instance",
    "xsd:http://www.w3.org/2001/XMLSchema"
} )]
cancelShipmentResponse1 cancelShipment(MarvelPress.Workflow.Business.RoyalShippingApi.cancelShipmentRequest1 request);     
Here's the more generic implementation:

public class EnvelopeNamespaceMessage : Message
{
    private readonly Message message;
    public string[] EnvelopeNamespaces { get; set; }
    public EnvelopeNamespaceMessage(Message message)
    {
        this.message = message;
    }
    public override MessageHeaders Headers
    {
        get { return this.message.Headers; }
    }
    public override MessageProperties Properties
    {
        get { return this.message.Properties; }
    }
    public override MessageVersion Version
    {
        get { return this.message.Version; }
    }
    protected override void OnWriteStartBody(XmlDictionaryWriter writer)
    {
        writer.WriteStartElement("Body", "http://schemas.xmlsoap.org/soap/envelope/");
    }
    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
    {
        this.message.WriteBodyContents(writer);
    }
    protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer)
    {
        writer.WriteStartElement("soapenv", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
        if (EnvelopeNamespaces != null)
        {
            foreach (string ns in EnvelopeNamespaces)
            {
                var tokens = ns.Split(new char[] {':'}, 2);
                writer.WriteAttributeString("xmlns", tokens[0], null, tokens[1]);
            }
        }
    }
}
public class EnvelopeNamespaceMessageFormatter : IClientMessageFormatter
{
    private readonly IClientMessageFormatter formatter;
    public string[] EnvelopeNamespaces { get; set; }
    public EnvelopeNamespaceMessageFormatter(IClientMessageFormatter formatter)
    {
        this.formatter = formatter;
    }
    public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
    {
        var message = this.formatter.SerializeRequest(messageVersion, parameters);
        return new EnvelopeNamespaceMessage(message) {EnvelopeNamespaces = EnvelopeNamespaces};
    }
    public object DeserializeReply(Message message, object[] parameters)
    {
        return this.formatter.DeserializeReply(message, parameters);
    }
}

[AttributeUsage(AttributeTargets.Method)]
public class EnvelopeNamespacesAttribute : Attribute, IOperationBehavior
{
    public string[] EnvelopeNamespaces { get; set; }
    public void AddBindingParameters(OperationDescription operationDescription,
        BindingParameterCollection bindingParameters)
    {
    }
    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
    {
            //var serializerBehavior = operationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
            IOperationBehavior serializerBehavior = operationDescription.Behaviors.Find<XmlSerializerOperationBehavior>();
            if (serializerBehavior == null)
                serializerBehavior = operationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>() ;
            if (clientOperation.Formatter == null)
                serializerBehavior.ApplyClientBehavior(operationDescription, clientOperation);
        IClientMessageFormatter innerClientFormatter = clientOperation.Formatter;
        clientOperation.Formatter = new EnvelopeNamespaceMessageFormatter(innerClientFormatter)
        {
            EnvelopeNamespaces = EnvelopeNamespaces
        };
    }
    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
    }
    public void Validate(OperationDescription operationDescription)
    {
    }
}
Summary
Ah, WCF == (love && hate).

You gotta love that you can hook into the pipeline and fix a problem like this and that the designers thought of millions of intricate ways to manage the process of manipulating messages. But man is this stuff difficult to discover this stuff or even to hook it up. I was lucky I found a reference to MessageFormatter in an obscure post referenced in a comment.

While the code to actually do the work is really simple there is a boat load of ceremony around all of this code to get it to actually fire. Plus an attribute has to be added to each and every Operation method and it also means I have to manually edit the generated WCF proxy Client interface. All of which is as far from 'transparent' as you can get.

But hey, at least I managed to get it to work and now we can get on with our lives actually talking to the service. I shudder to think what other lovely oddities we might run into with this service, but I leave that for another day.

I hope this information is useful to some of you, although I hope even more that you won't need it, because you know, you shouldn't have to do it this way! But as is often the case, we can't always choose what crappy services we have to  interact with on the server and workarounds are what we have to work with.

So I hope this has been useful… I know my previous posts on WCF formatting issues are among the most popular on this blog. Heck, I have a feeling I'll be revisiting this post myself in the not so distant future since problem SOAP services are a never-ending plague these days…

Create a Basic WCF Web HTTP Service


Windows Communication Foundation (WCF) allows you to create a service that exposes a Web endpoint. Web endpoints send data by XML or JSON, there is no SOAP envelope. This topic demonstrates how to expose such an endpoint.
Note
The only way to secure a Web endpoint is to expose it through HTTPS, using transport security. When using message-based security, security information is usually placed in SOAP headers and because the messages sent to non-SOAP endpoints contain no SOAP envelope, there is nowhere to place the security information and you must rely on transport security.

To create a Web endpoint

  1. Define a service contract using an interface marked with the ServiceContractAttributeWebInvokeAttribute and the WebGetAttribute attributes.
    C#
    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        [WebGet]
        string EchoWithGet(string s);
    
        [OperationContract]
        [WebInvoke]
        string EchoWithPost(string s);
    }
    
    Note
    By default, WebInvokeAttribute maps POST calls to the operation. You can, however, specify the HTTP method (for example, HEAD, PUT, or DELETE) to map to the operation by specifying a "method=" parameter. WebGetAttribute does not have a "method=" parameter and only maps GET calls to the service operation.
  2. Implement the service contract.
    C#
    public class Service : IService
    {
        public string EchoWithGet(string s)
        {
            return "You said " + s;
        }
    
        public string EchoWithPost(string s)
        {
            return "You said " + s;
        }
    }
    

To host the service

  1. Create a WebServiceHost object.
    C#
    WebServiceHost host = new WebServiceHost(typeof(Service), new Uri("http://localhost:8000/"));
    
  2. Add a ServiceEndpoint with the WebHttpBehavior.
    C#
    ServiceEndpoint ep = host.AddServiceEndpoint(typeof(IService), new WebHttpBinding(), "");
    
    Note
    If you do not add an endpoint, WebServiceHost automatically creates a default endpoint. WebServiceHost also adds WebHttpBehavior and disables the HTTP Help page and the Web Services Description Language (WSDL) GET functionality so the metadata endpoint does not interfere with the default HTTP endpoint.
    Adding a non-SOAP endpoint with a URL of "" causes unexpected behavior when an attempt is made to call an operation on the endpoint. The reason for this is the listen URI of the endpoint is the same as the URI for the help page (the page that is displayed when you browse to the base address of a WCF service).
    You can do one of the following actions to prevent this from happening:
    • Always specify a non-blank URI for a non-SOAP endpoint.
    • Turn off the help page. This can be done with the following code.
    C#
    ServiceDebugBehavior sdb = host.Description.Behaviors.Find<ServiceDebugBehavior>();
    sdb.HttpHelpPageEnabled = false;
    
  3. Open the service host and wait until the user presses ENTER.
    C#
    host.Open();
    Console.WriteLine("Service is running");
    Console.WriteLine("Press enter to quit...");
    Console.ReadLine();
    host.Close();
    
    This sample demonstrates how to host a Web-Style service with a console application. You can also host such a service within IIS. To do this, specify the WebServiceHostFactory class in a .svc file as the following code demonstrates.
          <%ServiceHost   
    language=c#  
    Debug="true"  
    Service="Microsoft.Samples.Service"  
    Factory=System.ServiceModel.Activation.WebServiceHostFactory%>  
    

To call service operations mapped to GET in Internet Explorer

  1. Open Internet Explorer and type "http://localhost:8000/EchoWithGet?s=Hello, world!" and press ENTER. The URL contains the base address of the service ("http://localhost:8000/"), the relative address of the endpoint (""), the service operation to call ("EchoWithGet"), and a question mark followed by a list of named parameters separated by an ampersand (&).

To call service operations in code

  1. Create an instance of System.ServiceModel.Web.WebChannelFactory within a using block.
    C#
    using (ChannelFactory<IService> cf = new ChannelFactory<IService>(new WebHttpBinding(), "http://localhost:8000"))
    
  2. Add WebHttpBehavior to the endpoint the ChannelFactory calls.
    C#
    cf.Endpoint.Behaviors.Add(new WebHttpBehavior());
    
  3. Create the channel and call the service.
    C#
    IService channel = cf.CreateChannel();
    
    string s;
    
    Console.WriteLine("Calling EchoWithGet via HTTP GET: ");
    s = channel.EchoWithGet("Hello, world");
    Console.WriteLine("   Output: {0}", s);
    
    Console.WriteLine("");
    Console.WriteLine("This can also be accomplished by navigating to");
    Console.WriteLine("http://localhost:8000/EchoWithGet?s=Hello, world!");
    Console.WriteLine("in a web browser while this sample is running.");
    
    Console.WriteLine("");
    
    Console.WriteLine("Calling EchoWithPost via HTTP POST: ");
    s = channel.EchoWithPost("Hello, world");
    Console.WriteLine("   Output: {0}", s);
    
  4. Close the WebServiceHost.
    C#
    host.Close();
    

Example

The following is the full code listing for this example.
C#
// Service.cs
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
using System.Text;

namespace Microsoft.ServiceModel.Samples.BasicWebProgramming
{
    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        [WebGet]
        string EchoWithGet(string s);

        [OperationContract]
        [WebInvoke]
        string EchoWithPost(string s);
    }
    public class Service : IService
    {
        public string EchoWithGet(string s)
        {
            return "You said " + s;
        }

        public string EchoWithPost(string s)
        {
            return "You said " + s;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            WebServiceHost host = new WebServiceHost(typeof(Service), new Uri("http://localhost:8000/"));
            try
            {
                ServiceEndpoint ep = host.AddServiceEndpoint(typeof(IService), new WebHttpBinding(), "");
                host.Open();
                using (ChannelFactory<IService> cf = new ChannelFactory<IService>(new WebHttpBinding(), "http://localhost:8000"))
                {
                    cf.Endpoint.Behaviors.Add(new WebHttpBehavior());
                    
                    IService channel = cf.CreateChannel();

                    string s;

                    Console.WriteLine("Calling EchoWithGet via HTTP GET: ");
                    s = channel.EchoWithGet("Hello, world");
                    Console.WriteLine("   Output: {0}", s);

                    Console.WriteLine("");
                    Console.WriteLine("This can also be accomplished by navigating to");
                    Console.WriteLine("http://localhost:8000/EchoWithGet?s=Hello, world!");
                    Console.WriteLine("in a web browser while this sample is running.");

                    Console.WriteLine("");

                    Console.WriteLine("Calling EchoWithPost via HTTP POST: ");
                    s = channel.EchoWithPost("Hello, world");
                    Console.WriteLine("   Output: {0}", s);
                    Console.WriteLine("");
                }

                Console.WriteLine("Press <ENTER> to terminate");
                Console.ReadLine();
                
                host.Close();
            }
            catch (CommunicationException cex)
            {
                Console.WriteLine("An exception occurred: {0}", cex.Message);
                host.Abort();
            }
        }
    }
}

Compiling the Code

When compiling Service.cs reference System.ServiceModel.dll and System.ServiceModel.Web.dll.