Dec 142010
 

I started seeing the following error in the AppFabric exceptions appearing during activation of my WF workflow.

Cannot create unknown type '{clr-namespace:MyActivityLibrary}MyCodeActivity'

The workflow completed all the steps of MyCodeActivity and the other code activities in my activity library. WF never complained; it was only an error visible in AppFabric. A Bing search resulted in the following suggestions:

  1. Create separate class libraries for activities and workflows (xamlx): already done.
  2. Place the activity library in the GAC: that should not be necessary so I skipped that one.
  3. Remove and add the activity from the workflow: again not necessary because I can see in the xaml that the namespace and the activity are fine and not appearing as a local assembly.

So it must be something else. Perhaps the unknown type error is not telling me the whole story and maybe it is failing to load/create another type referenced by my library? I went through my references and found a reference to a DLL that is not used by my activities. I removed the unnecessary reference and the errors did not return. Maybe the same thing is happening to you.

 Posted by at 9:26 pm
Dec 072010
 

In a previous post about my extensions for Enterprise Library pre-version 5, There was quite a bit of customized logic to create custom entities from a result set. Enterprise Library 5 now takes care of almost all of my customizations with the advent of accessors, row mappers, result set mappers, and parameter mappers. In this post I’ll show a few different ways to use out-of-the-box Enterprise Library 5 features to access data. In Part 2, I’ll also show a few of my own extensions that simply extend Enterprise Library and reduce repetitive code in my data access layer code.

Out-of-the-box Features

The most simplistic scenario exists when your database queries bring back results with column names exactly matching the property names. This is by far the easiest code to write with Enterprise Library, and requires far less code than with all previous versions. Here is a sample showing the default mapping of input parameters and result set columns/values using the new database extension method ExecuteSprocAccessor. You simply pass in the stored procedure name and the params, returning an IEnumerable of your custom entity (in this case, a Jeep object).

public Jeep GetJeepByID(int id)
{
    Database db = DatabaseFactory.CreateDatabase();
    IEnumerable<Jeep> jeeps = db.ExecuteSprocAccessor<Jeep>("GetJeepByID", id);
    return jeeps.First();
}

You can only use this method if all public properties of the custom entity can be mapped to a result set column/value. If any public property values cannot be mapped, you will receive a System.InvalidOperationException stating that the column was not found on the IDataRecord being evaluated. If your parameter or result set mapping becomes more complicated, you can specify a parameter mapper, row mapper, result set mapper, or a combination thereof to customize how your procedure is called, and how the results are interpreted. Here is an example of a custom parameter mapper and row mapper used to replicate the default mapping performed in the first example:

internal class JeepParameterMapper : IParameterMapper
{
    public void AssignParameters(DbCommand command, object[] parameterValues)
    {
        DbParameter parameter = command.CreateParameter();
        parameter.ParameterName = "@JeepID";
        parameter.Value = parameterValues[0];
        command.Parameters.Add(parameter);
    }
}

internal class JeepRowMapper : IRowMapper<Jeep>
{
    public Jeep MapRow(System.Data.IDataRecord row)
    {
        return new Jeep()
        {
            ID = row.GetInt32(0),
            Name = row.GetString(1),
            Description = row.GetString(2),
            Status = row.GetBoolean(3)
        };
    }
}

Below you will see the same task being performed in the first example, but this time with our custom mappers.

public Jeep GetJeepByIDWithMappers(int id)
{
    IParameterMapper jeepParameterMapper = new JeepParameterMapper();
    IRowMapper<Jeep> jeepRowMapper = new JeepRowMapper();

    Database db = DatabaseFactory.CreateDatabase();
    IEnumerable<Jeep> jeeps = db.ExecuteSprocAccessor<Jeep>("GetJeepByID", jeepParameterMapper, jeepRowMapper, id);
    return jeeps.First();
}

ResultSetMappers can be used to map more complex result sets to custom entities with deeper object graphs. Consider a stored procedure that returns multiple result sets similar to that seen in the following image. The first result set contains the custom entity details, and the second result set is some collection of child objects. In this case, we see an article with a child collection of article images.

article_resultset

You would have a hard time building up your custom entity without using an IDataReader and iterating through the result sets with .NextResult. ResultSetMappers allow you to code for this scenario. Below we’ll create a custom result set mapper for articles that will map all of the relevant result sets to the Article object.

internal class ArticleResultSetMapper : IResultSetMapper<Article>
{
    public IEnumerable<Article> MapSet(System.Data.IDataReader reader)
    {
        Dictionary<int, Article> articles = new Dictionary<int, Article>();

        Article article;
        ArticleImage articleImage;
        while (reader.Read())
        {
            article = new Article
            {
                ID = reader.GetInt32(0),
                Title = reader.GetString(1),
                Description = reader.GetString(2),
                Images = new Collection<ArticleImage>()
            };
            articles.Add(article.ID, article);
        }
        if (reader.NextResult())
        {
            while (reader.Read())
            {
                int articleID = reader.GetInt32(0);
                if (articles.ContainsKey(articleID))
                {
                    articleImage = new ArticleImage
                    {
                        DisplayOrder = reader.GetInt32(1),
                        Url = reader.GetString(2),
                        Caption = reader.GetString(3)
                    };
                    articles[articleID].Images.Add(articleImage);
                }
            }
        }

        return articles.Select(a => a.Value);
    }
}

Below you will see the code used to create a new IEnumerable<Article> using our ArticleResultSetMapper:

public Article GetArticleByID(int id)
{
    ArticleResultSetMapper articleResultSetMapper = new ArticleResultSetMapper();

    Database db = DatabaseFactory.CreateDatabase();
    IEnumerable<Article> articles = db.ExecuteSprocAccessor<Article>("GetArticleByID", articleResultSetMapper, id);
    return articles.First();
}

As you can probably tell, Enterprise Library 5 gives you more power and control over the mapping and generation of your custom entities. The previous version of my Enterprise Library extensions focused primarily on performing just the types of mappings that are now built into the product. After seeing just a few examples, you should be ready to jump into Enterprise Library 5 Data Access head first. In the next post, we’ll walk through usage scenarios for a few of my Enterprise Library extensions that makes these routine tasks easier to read, maintain, and train.

 Posted by at 4:42 am
Dec 042010
 

An ACE (Application Control Engine) Appliance is commonly referred to as simply being a load balancer. An ACE appliance provides many more features that are commonly employed. One of the most desirable features is SSL off-loading. When properly configured for offloading, the ACE will serve as a load balancer, and also take on the responsibility of processing all SSL requests made to a web server farm. In this configuration web farm server’s IIS does not need to worry about SSL at all. SSL traffic ends at the ACE, and only unencrypted HTTP traffic exists between the ACE and IIS. This is desirable because it provides a hardware solution to SSL processing and alleviates some load and configuration on the web servers and IIS.

While these ACE features are wonderful, it creates a little headache for WCF servers hosted on IIS behind the ACE. The problem shows itself as one of the following error messages as seen on the client end:

System.ArgumentException: The provided URI scheme ‘http’ is invalid; expected ‘https’. Parameter name: via

System.ServiceModel.ServiceActivationException: The requested service, ‘https://sub.mydomain.com/MyService.svc’ could not be activated. See the server’s diagnostic trace logs for more information.

System.ServiceModel.ProtocolException: The content type text/html; charset=utf-8 of the response message does not match the content type of the binding (application/soap+msbin1). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly.

System.Net.WebException: The remote server returned an error: (500) Internal Server Error.

The clients have to call the service with a URL similar to https://sub.mydomain.com/MyService.svc. The ACE handles the SSL, converts the request to an unencrypted HTTP request and passes it onto the web server. When the request reaches IIS and the WCF dispatcher, a service cannot be found that matches the HTTPS To address in the header because only HTTP service endpoints are being exposed. To make it work, you need to trick WCF into thinking its HTTP services can support transport security without actual transport or message protection. You also have to configure the service to listen on one address (HTTP) and respond to requests from another (HTTPS).

Enter the professionals. Michele Leroux Bustamante, of IDesign, wrote a few important articles in DevProConnections in late 2008 (links at the bottom of this post) that discuss this in detail and provide a solution. I have modified her solution slightly to provide a slightly easier configuration experience.

AssertEncryptionHttpTransportElement.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.ServiceModel.Configuration;
using System.ServiceModel.Channels;

namespace WcfExtensions
{
    public class AssertEncryptionHttpTransportElement : HttpTransportElement
    {
        private bool m_RequireClientCertificate = false;
        ConfigurationPropertyCollection m_PropCol = null;

        public override Type BindingElementType
        {
            get
            {
                return typeof(AssertEncryptionHttpTransportBindingElement);
            }
        }

        protected override TransportBindingElement CreateDefaultBindingElement()
        {
            AssertEncryptionHttpTransportBindingElement retval =
                new AssertEncryptionHttpTransportBindingElement(m_RequireClientCertificate);
            return retval;
        }

        protected override ConfigurationPropertyCollection Properties
        {
            get
            {
                if (m_PropCol == null)
                {
                    m_PropCol = base.Properties;
                    m_PropCol.Add(
			new ConfigurationProperty("requireClientCertificate",
				typeof(bool), false, null, null, ConfigurationPropertyOptions.None));
                }
                return m_PropCol;
            }
        }

        protected override BindingElement CreateBindingElement()
        {
            return new AssertEncryptionHttpTransportBindingElement(this.RequireClientCertificate);
        }

        [ConfigurationProperty("requireClientCertificate", DefaultValue = false)]
        public bool RequireClientCertificate
        {
            get
            {
                bool retval = (bool)base["requireClientCertificate"];
                return retval;
            }
        }
    }
}

AssertEncryptionHttpTransportBindingElement.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Xml;
using System.Net.Security;

namespace WcfExtensions
{
    public class AssertEncryptionHttpTransportBindingElement : HttpTransportBindingElement, ITransportTokenAssertionProvider, IPolicyExportExtension
    {
        private bool m_RequireClientCertificate = false;

        public AssertEncryptionHttpTransportBindingElement() : base() { }

        public AssertEncryptionHttpTransportBindingElement(bool requireClientCertificate)
            : base()
        {
            m_RequireClientCertificate = requireClientCertificate;
        }

        public override BindingElement Clone()
        {
            AssertEncryptionHttpTransportBindingElement retval = new AssertEncryptionHttpTransportBindingElement(m_RequireClientCertificate);
            return retval;
        }

        public override T GetProperty<T>(BindingContext context)
        {
            if (typeof(T) == typeof(ISecurityCapabilities))
            {
                AssertEncryptionSecurityCapabilities retval = new AssertEncryptionSecurityCapabilities();
                return (T)(object)retval;
            }
            else
                return base.GetProperty<T>(context);
        }

        #region ITransportTokenAssertionProvider Members

        public XmlElement GetTransportTokenAssertion()
        {
            string secpolNS0 = "http://schemas.xmlsoap.org/ws/2005/07/securitypolicy";
            //string secpolNS = "http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702";
            XmlDocument xmldoc = new XmlDocument();
            XmlElement TransTokAssert = xmldoc.CreateElement("sp", "HttpsToken", secpolNS0);
            TransTokAssert.SetAttribute("RequireClientCertificate", m_RequireClientCertificate.ToString().ToLower());

            return TransTokAssert;
        }

        #endregion

        public bool RequireClientCertificate
        {
            get
            {
                return this.m_RequireClientCertificate;
            }
            set
            {
                this.m_RequireClientCertificate = value;
            }
        }

        #region IPolicyExportExtension Members

        void IPolicyExportExtension.ExportPolicy(MetadataExporter exporter, PolicyConversionContext context)
        {
            HttpsTransportBindingElement httpsTBE = new HttpsTransportBindingElement();
            httpsTBE.RequireClientCertificate = m_RequireClientCertificate;
            ((IPolicyExportExtension)httpsTBE).ExportPolicy(exporter, context);
        }

        #endregion
    }
}

AssertEncryptionSecurityCapabilities.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;
using System.Net.Security;

namespace WcfExtensions
{
    internal class AssertEncryptionSecurityCapabilities : ISecurityCapabilities
    {
        #region ISecurityCapabilities Members

        public ProtectionLevel SupportedRequestProtectionLevel
        {
            get { return ProtectionLevel.EncryptAndSign; }
        }

        public ProtectionLevel SupportedResponseProtectionLevel
        {
            get { return ProtectionLevel.EncryptAndSign; }
        }

        public bool SupportsClientAuthentication
        {
            get { return false; }
        }

        public bool SupportsClientWindowsIdentity
        {
            get { return false; }
        }

        public bool SupportsServerAuthentication
        {
            get { return true; }
        }

        #endregion
    }
}

Hosting application configuration file:

<system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />

    <extensions>
      <bindingElementExtensions>
        <add name="assertEncryptionHttpTransportElement"
                type="WcfExtensions.AssertEncryptionHttpTransportElement, WcfExtensions" />
      </bindingElementExtensions>
    </extensions>

    <bindings>
      <customBinding>
        <binding name="MyService.customBinding0">
            <binaryMessageEncoding/>

            <!-- Replaces httpsTransport -->
            <assertEncryptionHttpTransportElement/>
        </binding>
      </customBinding>
    </bindings>

    <services>
      <service name="MyServices.MyService">
        <endpoint address="https://sub.mydomain.com/MyService.svc"
                  listenUri="http://myserver.mydomain.local/MyService.svc"
                  binding="customBinding"
                  bindingConfiguration="MyService.customBinding0"
                  contract="MyServices.IMyService" />
      </service>
    </services>
</system.serviceModel>

 

The server name in the “listenUri” must appear exactly as it does on the “You have created a service” page. This is typically the internal name of the server. The “address” must appear as the clients call the service (at the load balancer). The result of this configuration change is WCF responding to an unencrypted request sent to an HTTPS address while listening on an HTTP address. You can read about the internals of this solution in more detail in Michele’s articles referenced below.

You can download the project that compiles to give you the WcfExtensions DLL referenced above.

Resources:

 Posted by at 8:15 pm