After struggling with federated identity with wcf services for a couple of days I finally got it to work. The documentation for wif 4.5 was, to say the least, really thin. Got some tips from tinktecture and other blogs and are ready to do the next post in the Wcf series. Hope you find it useful
Wednesday, September 18, 2013
Wcf, wif and azure acs
Wednesday, September 11, 2013
How to programmatically configure WCF services and clients at runtime pt. 2.
In this post we will add support for a more advanced
configuration management and really take control of WCF. We will also add
support for REST service endpoints.
Centralized configuration – the simple version
For our “late configuration” of WCF, we want to add a shared
configuration store that all components in our system utilizes. In this
example, we will use XML and xpath to create a versioned configuration store
with inheritance. With inheritance, we mean that data is inherited from the
parent data set unless it is overridden in the children. A configuration set
represents a runtime environment like dev, test or prod.
The basic structure
The xml file consists of one or more ConfigurationSet, which
may contain as many services as needed. A service consist of one or more
endpoints with the appropriate binding configuration settings.
When we are reading from the file we use the value from the
first set that has a value in the requested node.
public string GetValue(string expression)
{
return
GetValueFromVersion(expression, Version);
}
private string GetValueFromVersion(string expression, string
version)
{
if
(string.IsNullOrEmpty(version)) return null;
var expr =
CreatExpression(expression, version);
var iterator =
Navigator.Select(expr);
if
(iterator.MoveNext())
return
iterator.Current.Value;
if (expression
== "ParentSet") return null;
return
GetValueFromVersion(expression, GetParent(version));
}
public IEnumerable
{
var expr =
CreatExpression(expression, Version);
var
iterator=Navigator.Select(expr);
var list=new
List
while
(iterator.MoveNext())
{
list.Add(iterator.Current.Value);
}
return list;
}
private XPathExpression CreatExpression(string expression,
string version)
{
var expr =
Navigator.Compile(FormatExpression(expression, version));
return expr;
}
//Creates an xpath expression by combining an incoming
expression part with the set selector query
private static string FormatExpression(string expression, string
version)
{
var expr =
string.Format("ConfigurationSets/ConfigurationSet[SetName='{0}']/{1}",
version, expression);
return expr;
}
|
To make it easy to create the needed expressions we create
our own little expression builder.
public static class ExpressionBuilder
{
public static
string GetEndpointNames(string serviceName)
{
return
string.Format("Services/EndpointConfig[ServiceName='{0}']//Endpoint/EndpointName",
serviceName);
}
public static
string GetEndpointConfigValue(string serviceName, string bindingName, string
attributeName)
{
return
string.Format("Services/EndpointConfig[ServiceName='{0}']//Endpoint[EndpointName='{1}']/{2}",
serviceName, bindingName, attributeName);
}
public static
string GetClientEndpoint(string serviceName)
{
return
string.Format("Services/EndpointConfig[ServiceName='{0}']/ActiveEndpoint",
serviceName);
}
}
|
With this, we have the necessary code to read config
settings and use it in our configuration code.
First we create a
helper class that loads and initializes our configuration store. Note that this
helper should include some kind of caching mechanism that allows the
application to get new settings without restart.
internal static class ConfigurationReader
{
internal static
VersionedDocumentReader Reader;
static
ConfigurationReader()
{
Reader = new
VersionedDocumentReader();
Reader.Load(ConfigurationManager.AppSettings["ConfigPath"]);
Reader.SetVersion(ConfigurationManager.AppSettings["ConfigSet"]);
}
}
|
To make it easier to configure and name the services we add
a new attribute that sets the service name on the interface. We access this
attribute instance at runtime using reflection and read the service name. If
the service interface doesn’t have this attribute set we use the interface name
as the service name.
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Interface, AllowMultiple = false, Inherited = true)]
public sealed class
ServiceNameAttribute : Attribute
{
public string
ServiceName { get; private set; }
public
ServiceNameAttribute(string serviceName)
{
ServiceName =
serviceName;
}
}
|
Changes to the code.
We need to pass the service name to the binding creators so
they can find the correct settings to use during configuration of the binding.
public interface IBindingBuilder
{
Binding
CreateBinding(string serviceName);
}
|
Then we add a helper method to the binding builders to read
data from the configuration store and inject the binding name.
private static string GetValue(string serviceName,string
valueName)
{
return
ConfigurationReader.Reader.GetValue(ExpressionBuilder.GetEndpointConfigValue(serviceName,
"basic", valueName));
}
|
public Binding CreateBinding(string serviceName)
{
return new
BasicHttpBinding
{
Name =
"basic",
AllowCookies = false,
HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
MaxBufferPoolSize =
int.Parse(GetValue(serviceName,"MaxBufferPoolSize")),
MaxReceivedMessageSize = int.Parse(GetValue(serviceName,
"MaxReceivedSize")),
MessageEncoding =
(WSMessageEncoding)Enum.Parse(typeof(WSMessageEncoding),
GetValue(serviceName,"MessageFormat")),
TextEncoding = Encoding.GetEncoding(GetValue(serviceName,
"TextEncoding")),
ReaderQuotas
= XmlDictionaryReaderQuotas.Max,
BypassProxyOnLocal = true,
UseDefaultWebProxy = false
};
}
|
And we add a new class for creating a REST binding
internal class RestBindingBuilder:IBindingBuilder
{
public Binding
CreateBinding(string serviceName)
{
var binding =
new WebHttpBinding(WebHttpSecurityMode.None)
{
AllowCookies = false,
HostNameComparisonMode =
HostNameComparisonMode.StrongWildcard,
MaxBufferPoolSize = int.Parse(GetValue(serviceName,
"MaxBufferPoolSize")),
MaxReceivedMessageSize = int.Parse(GetValue(serviceName,
"MaxReceivedSize"))
};
return
binding;
}
private static
string GetValue(string serviceName, string valueName)
{
return
ConfigurationReader.Reader.GetValue(ExpressionBuilder.GetEndpointConfigValue(serviceName,
"rest", valueName));
}
}
|
For the WebHttpBinding to work we need to do some additional
changes to the code, I will comment this when we get to these parts later.
We also need to read the address from the configuration
store by changing the AddressHelper class to take the service name and use this
to look for the value in the xml file.
internal static string GetFormattedAddress(string
bindingType,string serviceName)
{
return
String.Format(ConfigurationReader.Reader.GetValue(ExpressionBuilder.GetEndpointConfigValue(serviceName,
bindingType, "Address"))) + "/" + bindingType;
}
|
Update ServiceConfigurationImp
to get the service name and pass it to the binding builders.
Add a method called GetServiceName. You need to get the service
interface from the service type. I have done this the quick and dirty way her
and assuming that the serviceType only implements one service interface. It the
interface is decorated with the ServiceName attribute we use the value from it,
else we use the name of the serviceType.
private string GetServiceName(Type serviceType)
{
var attrib =
serviceType.GetInterfaces()[0].GetAttribute
if (attrib ==
null) return serviceType.Name;
return
attrib.ServiceName;
}
|
Update the GetBindingTypes to read from the configuration
store and not the config file as in the previous post.
private static IEnumerable
{
var
bindingTypes =
ConfigurationReader.Reader.GetValues(ExpressionBuilder.GetEndpointNames(serviceName));
return
bindingTypes;
}
|
Update CreateEndpoint to support WebHttpBinding, where we
set the default response format to json.
private static ServiceEndpoint CreateEndpoint(Binding binding,
EndpointAddress address, ContractDescription cd)
{
if (binding is
WebHttpBinding)
return new
WebHttpEndpoint(cd, address)
{
HelpEnabled = true,
DefaultOutgoingResponseFormat = WebMessageFormat.Json,
Binding = binding
};
return new
ServiceEndpoint(cd, binding, address);
}
|
This is basically the changes needed for the service side of
WCF.
On the client side we only need to do one small change in ServiceClientContainer.
It also needs to get the service name from the attribute and we need to get the
active client binding from the configuration store.
private string GetServiceName()
{
var
serviceType = typeof(T);
var attrib =
serviceType.GetAttribute
if (attrib ==
null) return serviceType.Name;
return
attrib.ServiceName;
}
|
internal ServiceClientContainer()
{
ServiceName =
GetServiceName();
BindingType =
GetBindingType();
Url =
AddressHelper.GetFormattedAddress(BindingType,ServiceName);
}
|
public ServiceClientContainer
{
ClientFactory
= CreateChannelFactory(ServiceName, Url);
if
(ClientFactory.Endpoint.Binding is WebHttpBinding)
ClientFactory.Endpoint.Behaviors.Add(new WebHttpBehavior());
return this;
}
|
Updating the config
files
There are just a few things we need to change in the config
files for the client and service. We need to add references to the location of
the xml configuration file and which set to use.
<add
key="ConfigPath" value="C:\Settings\sentralConfiguration.xml"/>
<add
key="ConfigSet" value="Demo"/>
|
Download the code here: http://jmp.sh/b/k0b3LRA0jxOz83L4baQ8
Tuesday, September 3, 2013
How to programmatically configure WCF services and clients at runtime pt. 1.
Windows Communication Foundation (WCF) is a framework from
Microsoft that helps developers build (web) services. WCF serves as an
abstraction layer and allows the developer to focus on _what_ to build, not on
all the how’s, if’s and but’s. Choosing transport, protocol and security is
done later [config time].
In this post, we will look into how to configure WCF programmatically
and postpone the choices to run-time. Note that the service needs to recycle in
order to obtain new config settings.
One challenge we meet in software development is
configurations sets that change from development, test and production
environments. Getting all the endpoints and settings correct in each
environment is time consuming and error prone.
One solution is to centralize the configuration and
introduce environmental variables to the server images. Let us say that we
create a windows server image and inject an appSettings value for environment
e.g. development. And we also might want to add a value that can resolve the
correct url’s for the services and endpoints needed in a given environment. Add
these settings to the machine.config file of your server image, this will make
the settings available to all services and applications running on the server.
<add key="Environment" value="Dev" >
<add key="UrlPrefix" value="Dev." > var endpointUrl=String.Format(“http://{0}MyService.MyCompany.com/Service.svc”,ConfigurationManager.AppSettings[“UrlPrefix”]); |
This neat little trick makes deployment easy and no
configuration transformation is needed. However, you are still stuck with the
wcf configuration from your development environment, unless you use config
transformations. Config transformations are still a static approach, changing
the settings means that you need to change the transformation and update the
config file in your service and the service clients.
This is where we will start to play with WCF and configuring
this runtime.
The service:
In this post we’ll only cover .svc hosted services. First
you need to create a service host factory, which is responsible for creating
the service host. During creation of the service host you can configure the
service and the available endpoints.
Please note that the sample code uses the really poor man’s
IoC (switch - new), in real life scenarios change this to your preferred IoC
container. In my production version of this, I use a custom lightweight IoC
container.
Service Host Factory
To be able to
configure the service at runtime you need to create a new service host factory.
Do this by overriding ServiceHostFactory’s CreateServiceHost method. In this
method you can control all aspects of the service you are hosting.
protected override ServiceHost CreateServiceHost(Type
serviceType, Uri[] baseAddresses)
{
var
serviceHost = new ServiceHost(serviceType, baseAddresses);
var
configurator = CreateConfigurator();
configurator.ConfigureServiceHost(serviceHost, serviceType);
SetBehavior(serviceHost);
return
serviceHost;
}
|
First you create the service host, just ‘new up’ the WCF
default implementation. We will add the endpoints to this later.
To prepare for more advanced scenarios in later posts we
will create an interface to handle the configuration of the endpoints. Create
an interface, IServiceConfiguration, with a method called ConfigureServiceHost.
After the endpoints have been added to the service host, we
want to set some behavior for the service, like open and close timeout values.
Building the endpoints
Each endpoint has its own binding, all bindings share the
same base type, Binding. This allows us to create an abstraction over this
using an interface, IBindingBuilder.
var bindingTypes =
ConfigurationManager.AppSettings["bindingTypes"].Split(';');
var
contractDescription =
ContractDescription.GetContract(serviceType.GetInterfaces()[0]);
contractDescription.Namespace = "http://demo.wcf.org/" +
serviceType.Name;
foreach (var
bindingType in bindingTypes)
{
var bindingBuilder =
GetBindingBuilder(bindingType);
var binding = bindingBuilder.CreateBinding();
var
endpoint = CreateEndpoint(binding, new
EndpointAddress(GetFormattedAddress(bindingType)), contractDescription);
serviceHost.AddServiceEndpoint(endpoint);
}
private static ServiceEndpoint CreateEndpoint(Binding binding,
EndpointAddress address, ContractDescription cd)
{
return new
ServiceEndpoint(cd, binding, address);
}
|
class BasicBindingBuilder : IBindingBuilder
{
public Binding
CreateBinding()
{
return new
BasicHttpBinding
{
Name =
"basic",
AllowCookies = false,
HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
MaxBufferPoolSize = int.MaxValue,
MaxReceivedMessageSize = int.MaxValue,
MessageEncoding = WSMessageEncoding.Mtom,
TextEncoding = Encoding.UTF8,
ReaderQuotas = XmlDictionaryReaderQuotas.Max,
BypassProxyOnLocal = true,
UseDefaultWebProxy = false
};
}
}
|
public Binding CreateBinding()
{
return new
WSHttpBinding(SecurityMode.None)
{
Name = "ws",
AllowCookies = false,
TransactionFlow = false,
HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
MaxBufferPoolSize = int.MaxValue,
MaxReceivedMessageSize =
int.MaxValue,
MessageEncoding = WSMessageEncoding.Text,
TextEncoding = Encoding.UTF8,
ReaderQuotas = XmlDictionaryReaderQuotas.Max,
BypassProxyOnLocal = true,
UseDefaultWebProxy = false
};
}
|
The values to configure the bindings with can be read from a
configuration store. I’ll post a simple version of a centralized configuration
store later.
Bringing it all
together
To enable the service to be configured by our new factory we
need to add one simple statement to the svc markup (marked in red).
<%@ ServiceHost Language="C#"
Debug="true" Service="Syrstad.Blog.WcfDemo.Service.DemoService"
CodeBehind="DemoService.svc.cs" Factory="Syrstad.Blog.WcfDemo.WcfConfigurationModule.HostFactory"
%>
|
When the development team is building both the service and
the client(s) it is a good thing to extract the interface and the data classes
in a separate assembly for easy distribution, through the build system, to the
projects that depend on it.
Configuring the client
The client also needs to be configured in the same way. We
want to ensure that resources used by WCF are properly cleaned, so we will
create a container for the proxy that we make disposable.
For ease of use we will create a factory for client proxy
containers, called ClientProxyFactory. This has one static method that returns
an initialized instance of the container. We implement the container as a
generic and uses the type parameter is the interface used in the service itself.
public static class ClientProxyFactory
{
public static
ServiceClientContainer
{
//Replace with
your IoC of choice
return new
ServiceClientContainer
}
}
|
The service container creates a new channel factory with the
appropriate settings collected from the interface and the config store. This factory
is used to create the actual proxy. The factory is kept as a instance field so
it will be a part of the dispose.
For binding creation, we reuse the BindingFactory used by
the service so that there is no possibility for configuration mismatch.
internal ServiceClientContainer()
{
ServiceName =
typeof(T).Name;
BindingType =
ConfigurationManager.AppSettings["useClientBinding"];
Url =
AddressHelper.GetFormattedAddress(BindingType);
}
public
ServiceClientContainer
{
ClientFactory
= CreateChannelFactory(ServiceName, Url);
return this;
}
internal
ChannelFactory
{
var builder =
GetProxyBuilder();
var
channelFactory = new
ChannelFactory
foreach
(OperationDescription op in channelFactory.Endpoint.Contract.Operations)
{
var
dataContractBehavior =
op.Behaviors[typeof(DataContractSerializerOperationBehavior)] as
DataContractSerializerOperationBehavior;
if
(dataContractBehavior != null)
{
dataContractBehavior.MaxItemsInObjectGraph = int.MaxValue;
}
}
return
channelFactory;
}
private static
IProxyBindingBuilder GetProxyBuilder()
{
return new
ProxyBindingBuilder();
}
|
Next we need a GetClient method that creates the proxy and
returns it to the client code.
public T GetClient()
{
if (Client ==
null) Client = ClientFactory.CreateChannel(new EndpointAddress(Url));
return Client;
}
|
This approach makes it easy and clean to create and use runtime
generated WCF proxies and ensure proper disposal of the underlying resources.
Download the demo solution and play with WCF. I apologize for
the use of regions in some of the source files; they are added as an effort to
make this post and the code easier to follow.
Please note that I have done some code cleaning (separated
some code into new methods) after I wrote this post.
The demo solution
Project
|
Description
|
Syrstad.Blog.WcfDemo
|
Contains WCF Service interface and data transfer objects for the
demo
|
Syrstad.Blog.WcfDemo.Client
|
The demo client
|
Syrstad.Blog.WcfDemo.Service
|
The demo service
|
Syrstad.Blog.WcfDemo.WcfConfigurationModule
|
The WCF configuration code. This will be updated in future posts
to include centralized config store and more binding builders
|
Labels:
.net,
Developement,
WCF
Location:
Kristiansand, Norge
Subscribe to:
Posts (Atom)