1. Simplest web service
2. Deployment
3. Web server
4. Terminology
5. Binding Types
6. Deployment options
7. Self host
8. Consuming the web service - client
9. Create client without service reference and app.config
10. Debugging WCF service from Visual Studio
11. web.config|web.debug.config|web.release.config
12. Data format
- Simple data types
- Composite data types
13. WCF as REST
14. Enable exceptions in output
15. Large messages
16. Extensibility-versioning
17. Performance
18. Testing
- Dependency injection (with Unity)
19. Security
- Turn off authentication (in config)
- Using SSL
- WCF: install certificate in local machine storage
20. WCF: Assign certificate to client in code
21. WCF: Add http header to request
WCF stands for Windows Communication Foundation and you read more about it on MSDN
Windows Communication Foundation (MSDN)
- Learning WCF (MSDN)
Getting Started Tutorial (MSDN)
How to articles (MSDN - patterns & practices)
Computer setup for WCF (with IIS,Firewall) (MSDN)
- Configuring HTTP and HTTPS for WCF (MSDN)
- Server Certificate Installation Instructions (MSDN)
1. Simplest web service
C# <%@ ServiceHost Language="C#" Debug="true" Service="WcfServiceSimpleNs.WcfServiceSimple" %> using System; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WcfServiceSimpleNs { [ServiceContract] public interface IWcfServiceSimple { [OperationContract] string GetData(int value); } public class WcfServiceSimple : IWcfServiceSimple { public string GetData(int value) { return string.Format("Simplest: Argument passed: {0}", value); } } }
2. Deployment
3. Web server
Create a folder, e.g. WcfSimple
Save the content and the web service code above to WcfSimple.svc and save it under WcfSimple folder.
You need to right click on the folder and select "Convert to application"
Make sure that you select the AppPool with Integrated Pipeline mode (When you use classic you wil get 500.21 Error)
Open web browser and type: http://localhost/WcfSimple/WcfSimple.svc
The browser will show you message: Metadata publishing for this service is currently disabled.
WCF web service requires configuration file. A minimum configuration file you can find below.
Note: if you do not have any service listed in your config at all, when debugging from Visual Studio with IIS Express or when hosting in IIS, the hosting server will detect if a service is available and expose it with basicHttpBinding
XML <configuration> <system.serviceModel> <services> <!-- Note: the service name must match the configuration name for the service implementation. --> <service name="WcfServiceSimpleNs.WcfServiceSimple" behaviorConfiguration="MyServiceTypeBehaviors" > <endpoint address="" binding="wsHttpBinding" contract="WcfServiceSimpleNs.IWcfServiceSimple" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name="MyServiceTypeBehaviors" > <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>Now the service appears in browser. In order to see WSDL, you need to type http://localhost/WcfSimple/WcfSimple.svc?wsdl
if you want to disable wsdl, set
XML <serviceMetadata httpGetEnabled="false" />Be aware that the when the service is hosted by IIS then the service reads its System.ServiceModel configuration from web.config, not from MyService.dll.config, that gets created by Visual Studio. MyService.dll.config would be used if the service is selfhosted.
4. Terminology
Address - specify location of the service. Client will need to set this to URL of the service.
Binding - defines how client communicates with the service (SOAP, REST, ..)
Contract - defines data transferred (IWcfServiceSimple interface)
5. Binding Types
wsHttpBinding - SOAP 1.2 (security, reliable messiging, sessionfull connections; heavier
webHttpBinding - REST (does not require a SOAP client to access the messages, XML or JSON retuned based on URL)
netTcpBinding - optimized and faster than wsHttpBinding, secure, intended for WCF-to-WCF communication
All bnding types on MSDN
6. Deployment options
2. .svc and .svc.cs file. You can split the .svc file above to WcfSimple.svc
C# <%@ ServiceHost Language="C#" Debug="true" Service="WcfServiceSimpleNs.WcfServiceSimple" %>and \App_Code\WcfServiceSimple.svc.cs files.
C# using System; ... namespace WcfServiceSimpleNs { public interface IWcfServiceSimple { ... } public class WcfServiceSimple : IWcfServiceSimple { ... } }3. You can compile .svc.cs to a .net dll binary (from Visual Studio or command line) and copy it to \bin folder of the web service. You may need to run c:\Windows\Microsoft.NET\Framework64\v3.0\Windows Communication Foundation\ServiceModelReg.exe -i to install WCF components.
7. Self host
1. Create console application
2. add reference to System.ServiceModel
C# // Must be executed with VS running As Administrator static void Main(string[] args) { Uri baseSvcAddress = new Uri("http://localhost:9000/MyServiceHost"); // Create the host using (ServiceHost host = new ServiceHost(typeof(WcfServiceSimple), baseSvcAddress)) { // Enable metadata ServiceMetadataBehavior svcMetadataBehavior = new ServiceMetadataBehavior(); svcMetadataBehavior.HttpGetEnabled = true; svcMetadataBehavior.MetadataExporter.PolicyVersion = PolicyVersion.Policy15; host.Description.Behaviors.Add(svcMetadataBehavior); // base service address will be used. host.Open(); Console.WriteLine("The service is running at {0}", baseSvcAddress); Console.WriteLine("Press 'Enter' to stop the service."); Console.ReadLine(); // Close the host. host.Close(); } }
8. Consuming the web service - client
When developing in Visual Studio In order to be able to create a client with a proxy, you do not need to deploy the service
1. In the same solution as the service project is located, Create a New project (e.g. a Console app)
2. Right click on References and select "Add Service Reference .."
3. Click "Discover/Service in Solution"
When consuming a public service
3. Enter the url of the running wcf service and click Go
4. Visual Studio will generate a Service References folder with a subfolder for your wcf service with about 9 files. Reference.cs file that represents the proxy to the service is created under the subfolder.
C# // create client using defined configuration from using (var client = new WcfServiceSimpleClient("BasicHttpBinding_IWcfServiceSimple")) { // call a service method with 1 argument string result = client.GetData(2); }
9. Create client without service reference and app.config
C# // create channel var myBinding = new WebHttpBinding(); var myEndpoint = new EndpointAddress("http://myurl.net"); var channelFactory = new ChannelFactory<IMyInterface>(myBinding, myEndpoint); channelFactory.Endpoint.Behaviors.Add(new WebHttpBehavior()); IComplianceFacade client = null; client = channelFactory.CreateChannel(); // make the call client.MyMethod(); // close channel channelFactory.Close();
10. Debugging WCF service from Visual Studio
- Press F5, WCF Test Client will be opened (VS 2013).
- Put a break point into the method that you want to debug
- Select the method in the left panel and click Invoke
Note 1: the service output must be in bin\ folder
Note 2: the <UseIIS> flag in .csproj file must be set to "true" in order the test client starts
Note 3: if you have your own client in the same solution, you can either start both the service and your own client by right clicking on the solution and selecting "Set StartUp Projects..." or just select the client as startup project and VS will start IIS with Service automatically
Note 4: you need to have an endpoint that exposes SOAP based binding (BasicHttpBinding or wsHttpBinding) to see the service in WCF Test Client
Note 5: Visaul Studio starts IIS Express. Make sure that it gets closed before you start the updated service (right on the IIS Express icon in the Task bar, click Exit).
11. web.config|web.debug.config|web.release.config
This will not happen during build (F5 in Visual Studio).
Usage: It is useful e.g. for replacing connection string (from local dev DB used during development when starting the service from Visual Studio) to production DB when deploying to production.
web.config
XML <connectionStrings> <add name="MyDB" connectionString="Data Source=.;Initial Catalog=DevelopmentDB;Integrated Security=True" /> </connectionStrings> </code>web.release.config
XML <connectionStrings> <add name="MyDB" connectionString="Data Source=ProductionSQLServer;Initial Catalog=ProductionDB;Integrated Security=True" xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/> </connectionStrings>If the the same action is required to happen during build, there are multiple ways how to run the transformation described here .
12. Data format
Simple data types
C# public string MyMethod(int myIntValue, string myStringValue) { return string.Format("Arguments passed: {0} {1}", myIntValue, myStringValue); }here is data send as request:
XML <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IWcfServiceSimple/MyMethod</Action> </s:Header> <s:Body> <MyMethod xmlns="http://tempuri.org/"> <myIntValue>2</myIntValue> <myStringValue>abc</myStringValue> </MyMethod> </s:Body> </s:Envelope>and as response:
XML <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header /> <s:Body> <MyMethodResponse xmlns="http://tempuri.org/"> <MyMethodResult>Arguments passed: 2 abc</MyMethodResult> </MyMethodResponse> </s:Body> </s:Envelope>
Composite data types
C# [DataContract] public class MyCompositeType { bool boolValue = true; string stringValue = "Hello "; [DataMember] public bool BoolValue { get { return boolValue; } set { boolValue = value; } } [DataMember] public string StringValue { get { return stringValue; } set { stringValue = value; } } } public MyCompositeType GetDataUsingDataContract(MyCompositeType composite) { return composite; }here is data send as request:
XML <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IWcfServiceSimple/GetDataUsingDataContract</Action> </s:Header> <s:Body> <GetDataUsingDataContract xmlns="http://tempuri.org/"> <composite xmlns:d4p1="http://schemas.datacontract.org/2004/07/WcfServiceSimple" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <d4p1:BoolValue>false</d4p1:BoolValue> <d4p1:StringValue>abc</d4p1:StringValue> </composite> </GetDataUsingDataContract> </s:Body> </s:Envelope>and as response:
XML <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header /> <s:Body> <GetDataUsingDataContractResponse xmlns="http://tempuri.org/"> <GetDataUsingDataContractResult xmlns:a="http://schemas.datacontract.org/2004/07/WcfServiceSimple" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <a:BoolValue>false</a:BoolValue> <a:StringValue>abc</a:StringValue> </GetDataUsingDataContractResult> </GetDataUsingDataContractResponse> </s:Body> </s:Envelope>
13. WCF as REST
In order to create a WCF REST service that would allow access from web browser like mydomain.net/myservice/myresource using HTTP verbs like GET, POST, PUT, DELETE it is necessary to
1. Attribute the web method with [WebGet] or [WebInvoke] (POST, PUT or DELETE)
C# [OperationContract] [WebGet(UriTemplate = "MyMethodRest/{myStringValue}/{myStringValue2}")] string MyMethodRest(string myStringValue, string myStringValue2);
2. Define an end point with that is configured with and uses webHttpBinding
XML <services> <service name="WcfServiceSimple.Service1"> <endpoint behaviorConfiguration="webBehavior" binding="webHttpBinding" contract="WcfServiceSimple.IWcfServiceSimple" /> </service> </services> <behaviors> <endpointBehaviors> <behavior name="webBehavior" <webHttp /> </behavior> </endpointBehaviors> </behaviors>Such a method can be called from browser as:
XML http://localhost:25719/WcfServiceSimple.svc/MyMethodRest?myStringValue=abc&myStringValue2=def http://localhost:25719/WcfServiceSimple.svc/MyMethodRest/abc/defResult in Browser
XML <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Arguments passed: abc def</string>!!!If UriTemplate is used then all argumentw that are used in the template must be type of string.
If JSON is required, it can defined as below
C# [WebGet(ResponseFormat = WebMessageFormat.Json)] // or WebMessageFormat.Xml
14. Enable exceptions in output
C# [ServiceBehavior(IncludeExceptionDetailInFaults = true)] public class WcfServiceSimple : IWcfServiceSimple2. config file
XML <system.serviceModel> <services> <service name="ServiceNamespace.ServiceClassName" behaviorConfiguration ="DebugEnabled"> </service> </services> <behaviors> <serviceBehaviors > <behavior name="DebugEnabled"> <serviceDebug includeExceptionDetailInFaults="True"/> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/> </system.serviceModel>
15. Large messages
XML <system.serviceModel> <bindings> <netTcpBinding> <binding name="largeMessageBinding" receiveTimeout="00:10:00" sendTimeout="00:10:00" maxReceivedMessageSize="1000485760" > <readerQuotas maxArrayLength="1000485760"/> </binding> </netTcpBinding> </bindings> <services> <service name="largeMessageService" <endpoint binding="netTcpBinding" bindingConfiguration="largeMessageBinding" ... /> </service> <services> </system.serviceModel>
16. Extensibility-versioning
If you change the name of a field on the server side, you can still make a call, but the old name on the client side will not be populated.
The same applies if the composite type is passed as an argument or a return type.
17. Performance
weblog.west-wind.com
blogs.msdn.microsoft.com
18. Testing
Dependency injection (with Unity)
Install Unity Nuget package
(it will add 3 Microsoft.Practices.Unity.*.dlls and Microsoft.Practices.ServiceLocation.dll)
Assuming you have a class that connects to an external repository (e.g. a database) and you want to mock the call for testing.
C# public interface IExternalRepository { int GetData(); } public class ExternalRepository : IExternalRepository { public int GetData() { int result = // read from a database return result; } }and a service depending on the repository
C# public class MyUnityService : IWcfServiceSimple { public string GetData() { return _externalRepository.GetData(); } }There are 2 options how to let unity to inject the classes:
1. Create Dependency Injection Factory with static constructor and add contructor to EACH dependency class
C#
using Microsoft.Practices.Unity;
public class DependencyFactory
{
private static IUnityContainer _container { get; set; }
static DependencyFactory()
{
_container = new UnityContainer();
container.RegisterType<IExternalRepository, ExternalRepository>();
}
public static T Resolve<T>()
{
T result = default(T);
if (_container.IsRegistered(typeof(T)))
{
result = _container.Resolve<T>();
}
return result;
}
}
and add static constructors to the WCF service
C# public class MyUnityService : IWcfServiceSimple { private readonly IExternalRepository _externalRepository; public MyUnityService() : this(DependencyFactory.Resolve<IExternalRepository>()) { } public MyUnityService(IExternalRepository externalRepository) { _externalRepository = externalRepository; } public string GetData() { return _externalRepository.GetData(); } }The example above demonstrates what happens behind the scene in the option #2.
2. Use Factory attribute in .svc file.
XML <%@ ServiceHost Language="C#" Debug="true" Factory="WcfServiceSimpleNs.WcfServiceHostFactory" Service="WcfServiceSimpleNs.WcfServiceSimple2" CodeBehind="WcfServiceSimple2.svc.cs" %>This requires to write a class inherited from ServiceHost
C# public partial class WcfServiceHost : ServiceHost { public WcfServiceHost(IUnityContainer container, Type serviceType, params Uri[] baseAddresses) : base(container.Resolve(serviceType), baseAddresses) { } public WcfServiceHost(IUnityContainer container, Type serviceType) : base(container.Resolve(serviceType)) { } }and the factory inherited from ServiceHostFactory
C# public class WcfServiceHostFactory : ServiceHostFactory { private readonly IUnityContainer _container; public WcfServiceHostFactory() { _container = new UnityContainer(); _container.RegisterType<IExternalRepository, ExternalRepository>(); } protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses) { return new WcfServiceHost(_container, serviceType, baseAddresses); } }The service must be marked with
C# [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] public class WcfServiceSimple2 : IWcfServiceSimple2 { }In both cases, when the WCF service class gets instatiated, the dependency classes (ExternalRepository) will be injected.
The second case does not require to write special constructors for each dependency class.
19. Security
How to: Authenticate with a User Name and Password (MSDN)
Message Security User Name (MSDN - Example of console client with IIS hosted service)
Turn off authentication (in config)
XML <system.serviceModel> <bindings> <wsHttpBinding> <binding name="CustomBinding"> <security mode="None"> <message establishSecurityContext="false"/> <transport clientCredentialType="None"/> </security> </binding> </wsHttpBinding> </bindings> <service> <endpoint ... binding="wsHttpBinding" bindingConfiguration="CustomBinding" > </endpoint> </service> .... <system.serviceModel>
Using SSL
1. Create binding behavior for https
XML <bindings> <wsHttpBinding> <binding name="secureBinding"> <security mode="Transport"> <transport clientCredentialType="None" /> </security> </binding> </wsHttpBinding> </bindings>and refer to this behavior from your endPoint
2. Enable https
XML <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpsGetEnabled ="True" /> </behavior> </serviceBehaviors> </behaviors>3. Update address in your client to use https
WCF: install certificate in local machine storage
Start mmc Go to File / Add/Remove Snap-in... Double click on Certificates Select Computer account, Next, Finish, OK In Console Window right click on Certificates/Personal/Certificates Select All Tasks/Import Select your certificate file from
ASP.net roles configuration in Visual Studio 2013
Start IIS express (not as admin) - works with .net 4
iisexpress.exe /path:C:\Windows\Microsoft.NET\Framework\v4.0.30319\ASP.NETWebAdminFiles /vpath:"/ASP.NETWebAdminFiles" /port:8082 /clr:4.0 /ntlm
Open ASP configuration (d:\mySolutionFolder is the path where .sln is located)
http://localhost:8082/asp.netwebadminfiles/default.aspx?applicationPhysicalPath=d:\mySolutionFolder\&applicationUrl=/
You will be ask to enter your Windows credentials. If you are logged to the domain, your full user name will look like "domainName\userName"
If you see the error (probably after VS 2015 installed):
Compiler Error Message: CS0122: 'System.Configuration.StringUtil' is inaccessible due to its protection level
Replace Line 989 in C:\Windows\Microsoft.NET\Framework\v4.0.30319\ASP.NETWebAdminFiles\WebAdminPage.cs
string appId = (String.Concat(appPath, appPhysPath).GetHashCode()).ToString("x", CultureInfo.InvariantCulture);
20. WCF: Assign certificate to client in code
C#
using System.Security.Cryptography.X509Certificates;
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificatesFound = store.Certificates.Find(X509FindType.FindBySubjectName, (object)"MyCertificateName", true);
store.Close();
channelFactory.Credentials.ClientCertificate.Certificate = certificatesFound[0];
21. WCF: Add http header to request
C# public class HttpHeaderEndpointBehavior : IEndpointBehavior { private readonly IDictionary<string, string> _headers; public HttpHeaderEndpointBehavior(IDictionary<string, string> headers) { _headers = headers; } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { var inspector = new HttpHeaderClientMessageInspector(_headers); clientRuntime.MessageInspectors.Add(inspector); } // implementation of abstract nmethods public void Validate(ServiceEndpoint endpoint) { } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } } public class HttpHeaderClientMessageInspector : IClientMessageInspector { private readonly IDictionary<string, string> _headers; public HttpHeaderClientMessageInspector(IDictionary<string, string> headers) { _headers = headers; } public object BeforeSendRequest(ref Message request, IClientChannel channel) { HttpRequestMessageProperty httpRequestMessage; object httpRequestMessageObject; if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject)) { httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty; if (httpRequestMessage != null) { foreach (var h in _headers) { httpRequestMessage.Headers[h.Key] = h.Value; } } } return null; } public void AfterReceiveReply(ref Message reply, object correlationState) { } } // add header to channel factory before creating the channel // HttpHeaderEndpointBehavior will be called before each request var headerDict = new Dictionary<string, string>(); headerDict.Add("MyHeaderKey", "MyHeaderValue"); facadeChannelFactory.Endpoint.Behaviors.Add(new HttpHeaderEndpointBehavior(headerDict));