Lesser-Known NHibernate Features: Mapping By Convention

Did you know that NHibernate, like other O/RMs out there, allows you to map classes by convention? Yes, it’s true… Smile Let me show you how!

First, you need to create an instance of the appropriately-named ConventionModelMapper:

var mapper = new ConventionModelMapper();

Next, you need to tell it where is the assembly (or assemblies) containing the entities you want to map:

var mappings = mapper.CompileMappingFor(typeof(MyEntity).Assembly.GetExportedTypes());

Finally, you need to add the generated mappings to a Configuration instance:

cfg.AddMapping(mappings);

That’s all it takes! Really! Winking smile

Granted, this is very convenient, but we don’t know much what is happening inside; for example, what id generation strategy is it using? By default, it uses native, which means it will use the native strategy of the database engine currently being used – identity for SQL Server and MySQL, sequence for Oracle and PostgreSQL, etc. If you wish to override this, you certainly can:

mapper.BeforeMapClass += (modelInspector, type, classCustomizer) =>

{

    classCustomizer.Id(x =>

    {

        x.Generator(Generators.HighLow);

    });

};

This tells the mapper to use the high-low strategy for all entities.

What if you want to change the default naming of columns and tables? Well, you have two options:

  1. Provide an handler for the BeforeMapClass or BeforeMapProperty events and in it change the name of the physical object:
    mapper.BeforeMapClass += (modelInspector, type, classCustomizer) =>

    {

        classCustomizer.Table(this.GetTableName(type.Name));

    };

    
    

    mapper.BeforeMapProperty += (modelInspector, member, propertyCustomizer) =>

    {

        propertyCustomizer.Column(this.GetColumnName(member.LocalMember.Name));

    };

  2. Provide your own implementation of INamingStrategy to NHibernate:
    public class CustomNamingStrategy : INamingStrategy

    {

        public String ClassToTableName(String className)

        {

            return className;

        }

    
    

        public String ColumnName(String columnName)

        {

            return columnName;

        }

    
    

        public String LogicalColumnName(String columnName, String propertyName)

        {

            return columnName;

        }

    
    

        public String PropertyToColumnName(String propertyName)

        {

            return propertyName;

        }

    
    

        public String PropertyToTableName(String className, String propertyName)

        {

            return propertyName;

        }

    
    

        public String TableName(String tableName)

        {

            return tableName;

        }

    }

    
    

    cfg.SetNamingStrategy(new CustomNamingStrategy());

In general, you can trust NHibernate’s judgement, but if you wish, you can override other aspects of the mapping by providing handlers to the many events of ConventionModelMapper. Say, for example, that you want to exclude (or include) only certain classes from an assembly, you can provide an handler for the IsEntity event:

mapper.IsEntity += (type, @default, assemblyName) =>

{

    return typeof(IEntity).IsAssignableFrom(type);

};

Or you want to configure a collection as a set rather than a bag:

mapper.IsBag += (member, @default) =>

{

    return false;

};


mapper.IsSet += (member, @default) =>

{

    return true;

};

Or even set a collection as not lazy and use inner join fetching:

mapper.BeforeMapSet += (modelInspector, member, propertyCustomizer) =>

{

    propertyCustomizer.Lazy(CollectionLazy.NoLazy);

    propertyCustomizer.Fetch(CollectionFetchMode.Join);

};

 

Adding Custom Validation Messages in Silverlight

Sometimes it is useful to add our own validation messages to a Silverlight control, normally because we are doing custom validation through code, not through the reguar IDataErrorInfo, INotifyDataErrorInfo or ValidationAttributes. This is not straightforward, but it is possible. An example might be:

//this class is used for throwing an exception upon binding

//has to be public, doesn't need to implement any interfaces

public class TagModelError

{

    private readonly String errorMessage;

 

    public TagModelError(String errorMessage)

    {

        this.errorMessage = errorMessage;

    }

 

    public Object Tag

    {

        get

        {

            return new Object();

        }

        set

        {

            throw new ValidationException(this.errorMessage);

        }

    }

}

 

//extension method for adding validation messages on a control

public static void AddValidationError(this Control control, String errorMessage)

{

    var expression = elm.GetBindingExpression(FrameworkElement.TagProperty);

 

    if (expression == null)

    {

        expression = control.SetBinding(FrameworkElement.TagProperty, new Binding("Tag")

        {                

            Mode = BindingMode.TwoWay,

            ValidatesOnExceptions = true,

            UpdateSourceTrigger = UpdateSourceTrigger.Explicit,

            Source = new TagModelError(errorMessage)

        }) as BindingExpression;

    }

 

    expression.UpdateSource();

}

In this example I am using the TagProperty, because it is seldom used, but you can use whatever you like. The trick here is to force the update of a bound object, which throws an exception, which in turn is propagated as a validation error.

Silverlight Transform Markup Extension

<ComboBox ItemsSource="{Binding ItemsSource, ElementName=MyDataSource}, Converter={StaticResource MyConverter}}" Width="100" Height="30"/>

<ComboBox ItemsSource="{Binding ItemsSource, ElementName=MyDataSource}, Converter={StaticResource MyConverter}}" Width="100" Height="30"/>

Did you ever have one of those situations where you need to apply a data source to a control, but also convert the each of the data source elements?

Normally we set the data source to the DataContext or ItemsSource property and add an item template where we specify the converter, but sometimes this can’t be done this way, for one reason or the other. So, I worked out a simple solution: the TransformedItemSource! What it does is, it allows us to specify a converter for each of the source values in the data source collection, not the data source as a whole.

Here it is:

public class TransformedItemSource : DependencyObject, IEnumerable

{

    public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(TransformedItemSource), new PropertyMetadata(null));

    public static readonly DependencyProperty ConverterProperty = DependencyProperty.Register("Converter", typeof(IValueConverter), typeof(TransformedItemSource), new PropertyMetadata(null));

    public static readonly DependencyProperty ParameterProperty = DependencyProperty.Register("Parameter", typeof(Object), typeof(TransformedItemSource), new PropertyMetadata(null));

    public static readonly DependencyProperty CultureProperty = DependencyProperty.Register("Culture", typeof(CultureInfo), typeof(TransformedItemSource), new PropertyMetadata(CultureInfo.CurrentCulture));

    public static readonly DependencyProperty TargetTypeProperty = DependencyProperty.Register("TargetType", typeof(Type), typeof(TransformedItemSource), new PropertyMetadata(null));

 

    public Type TargetType

    {

        get

        {

            return this.GetValue(TargetTypeProperty) as Type;

        }

        set

        {

            this.SetValue(TargetTypeProperty, value);

        }

    }

 

    public Object Parameter

    {

        get

        {

            return this.GetValue(ParameterProperty);

        }

        set

        {

            this.SetValue(ParameterProperty, value);

        }

    }

 

    public CultureInfo Culture

    {

        get

        {

            return this.GetValue(CultureProperty) as CultureInfo;

        }

        set

        {

            this.SetValue(CultureProperty, value);

        }

    }

 

    public IEnumerable ItemsSource

    {

        get

        {

            return this.GetValue(ItemsSourceProperty) as IEnumerable;

        }

        set

        {

            this.SetValue(ItemsSourceProperty, value);

        }

    }

 

    public IValueConverter Converter

    {

        get

        {

            return this.GetValue(ConverterProperty) as IValueConverter;

        }

        set

        {

            this.SetValue(ConverterProperty, value);

        }

    }

 

    IEnumerator IEnumerable.GetEnumerator()

    {

        foreach (var current in this.ItemsSource ?? new Object[0])

        {

            var targetType = this.TargetType ?? ((current != null) ? current.GetType() : null);

 

            yield return this.Converter.Convert(current, targetType, this.Parameter, this.Culture);

        }

    }

}

It inherits from DependencyObject, this is so that I can apply bindings to its properties. It will try to iterate through all the items in the ItemsSource property and convert one at a time using the supplied converter, culture and parameter. An example might be:

<UserControl.Resources>

    <my:TransformedItemSource x:Key="MyTransformer" ItemsSource="{Binding ItemsSource, ElementName=MyDatasource}" Converter="{StaticResource MyConverter}"/>

</UserControl.Resources>

<StackPanel x:Name="LayoutRoot">

    <ComboBox ItemsSource="{StaticResource MyTransformer}" Width="100" Height="30"/>

<StackPanel>

A nice way to use it is through a markup extension:

public class TransformExtension : DependencyObject, IMarkupExtension<IEnumerable>

{

    public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(TransformExtension), new PropertyMetadata(null));

    public static readonly DependencyProperty ConverterProperty = DependencyProperty.Register("Converter", typeof(IValueConverter), typeof(TransformExtension), new PropertyMetadata(null));

    public static readonly DependencyProperty ParameterProperty = DependencyProperty.Register("Parameter", typeof(Object), typeof(TransformExtension), new PropertyMetadata(null));

    public static readonly DependencyProperty CultureProperty = DependencyProperty.Register("Culture", typeof(CultureInfo), typeof(TransformExtension), new PropertyMetadata(CultureInfo.CurrentCulture));

    public static readonly DependencyProperty TargetTypeProperty = DependencyProperty.Register("TargetType", typeof(Type), typeof(TransformExtension), new PropertyMetadata(null));

 

    public Type TargetType

    {

        get

        {

            return this.GetValue(TargetTypeProperty) as Type;

        }

        set

        {

            this.SetValue(TargetTypeProperty, value);

        }

    }

 

    public Object Parameter

    {

        get

        {

            return this.GetValue(ParameterProperty);

        }

        set

        {

            this.SetValue(ParameterProperty, value);

        }

    }

 

    public CultureInfo Culture

    {

        get

        {

            return this.GetValue(CultureProperty) as CultureInfo;

        }

        set

        {

            this.SetValue(CultureProperty, value);

        }

    }

 

    public IEnumerable ItemsSource

    {

        get

        {

            return this.GetValue(ItemsSourceProperty) as IEnumerable;

        }

        set

        {

            this.SetValue(ItemsSourceProperty, value);

        }

    }

 

    public IValueConverter Converter

    {

        get

        {

            return this.GetValue(ConverterProperty) as IValueConverter;

        }

        set

        {

            this.SetValue(ConverterProperty, value);

        }

    }

 

    public IEnumerable ProvideValue(IServiceProvider serviceProvider)

    {

        return new TransformedItemSource { ItemsSource = this.ItemsSource, Parameter = this.Parameter, Culture = this.Culture, Converter = this.Converter };

    }

}

The TransformExtension class just delegates all of the work to the TransformedItemSource, but because it is a markup extension, it can be used as:

<ComboBox ItemsSource="{Binding ItemsSource, ElementName=MyDataSource, Converter={StaticResource MyConverter}}" Width="100" Height="30"/>

Enjoy!

Comunidades Portuguesas de Desenvolvimento

(This post is in Portuguese only)

Para quem não conhecer, existem as seguintes comunidades portuguesas de desenvolvimento e afins:

Se tiverem conhecimento de outras, por favor, avisem!

Lesser-Known NHibernate Features: LINQ Extensions

With NHibernate, you are not bound by the out-of-the box methods that LINQ provides, and their default translations to SQL. I already mentioned that you can add your own extension methods, with minimum work:

public static class StringExtensions

{

    [LinqExtensionMethod("FREETEXT")]

    public static Boolean Freetext(this String propertyName, String value)

    {

        return (propertyName.ToUpper().Contains(value.ToUpper()));

    }

}

For this example, I am creating an extension for the FREETEXT T-SQL function, which is one of the ways by which we can do full-text searching. All it takes is the LinqExtensionMethodAttribute applied to a method, with the name for the database function (can be different from the method name), and that’s it! NHibernate will try to match the parameters:

   1: var result = session.Query<MyEntity>().Where(x => x.Name.Freetext("something") == true).ToList();

Yes… Entity Framework let’s you do this… kind of… only for some functions!

Addendum

As Paulo Morgado (@paulomorgado) pointed out, line “propertyName.ToUpper().Contains(value.ToUpper())” should really be “propertyName.IndexOf(value, StringComparison.IgnoreCase)“, because it avoids string allocations and in general is much better.

Thanks, Paulo! 😉

Day Against DRM 2015 Campaign

For those who don’t know, today is the International Day Against DRM!

To celebrate that, Packt Publishing is offering all their ebooks and videos for only 10$ for the next 24 hours: http://bit.ly/1zMa8Sg.

You can find more information on their site: https://www.packtpub.com/packt/offers/day-against-drm.

2015 Banner

So, go get them! The campaign only lasts 24 hours! Winking smile

Silverlight Method Data Source Markup Extension

A quick way to obtain a data source from a method through a markup extension, without any code:

<ComboBox ItemsSource="{my:MethodDataSource MethodName=GetItems}"/>

Implementation is simple:

public class MethodDataSourceExtension : DependencyObject, IMarkupExtension<Object>

{

    public static readonly DependencyProperty TargetProperty = DependencyProperty.Register("Target", typeof(Object), typeof(MethodDataSourceExtension), new PropertyMetadata(null));

    public static readonly DependencyProperty ArgumentProperty = DependencyProperty.Register("Argument", typeof(Object), typeof(MethodDataSourceExtension), new PropertyMetadata(null));


    public String MethodName { get; set; }


    public Object Target

    {

        get { return this.GetValue(TargetProperty); }

        set { this.SetValue(TargetProperty, value); }

    }


    public Object Argument

    {

        get { return this.GetValue(ArgumentProperty); }

        set { this.SetValue(ArgumentProperty, value); }

    }


    public Object ProvideValue(IServiceProvider serviceProvider)

    {

        var rootObjectProvider = serviceProvider.GetService<IRootObjectProvider>();

        var target = (this.Target ?? rootObjectProvider.RootObject);

        var method = target.GetType().GetMethod(this.MethodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public);

        var args = (this.Argument != null) ? new Object[] { this.Argument } : null;


        return method.Invoke((method.IsStatic == true) ? null : target, args);

    }

}

The MethodDataSourceExtension has three properties:

  • MethodName: the name of a method;
  • Target: the optional element from which the data source method will be retrieved; if not specified, the containing control (Page, UserControl, etc) will be used;
  • Argument: an optional argument to the data source method.

Because we might want to use bindings, the MethodDataSourceExtension class inherits from DependencyObject. This allows us to have:

<ComboBox ItemsSource="{my:MethodDataSource MethodName=GetItems, Target={Binding ElementName=SomeControl}}"/>

We can call any method, static or instance, as long as it is public. It can have a single parameter or no parameter at all.

Lesser-Known NHibernate Features: Serializing Configuration

This isn’t exactly a feature of NHibernate, but it is something that you can do with it and most people isn’t aware of.

If you have a big number of classes and mappings in your domain, adding all of them to a Configuration instance can take some time.

NHibernate allows you to serialize the Configuration instance with all the mappings that it contains so that you can deserialize it later, which can result in reduced startup time. The disadvantage is that if you change any of the mappings, you have to discard the serialized file and build a new one.

Here’s how you can do it:

var serializer = new BinaryFormatter();

 

//serialize

using (var stream = File.OpenWrite("Configuration.bin"))

{

    serializer.Serialize(stream, cfg);

}

 

//deserialize

using (var stream = File.OpenRead("Configuration.bin"))

{

    cfg = serializer.Deserialize(stream) as Configuration)

}

Silverlight Resource Markup Extension

In case you want to use resources in your Silverlight applications, you have a couple of options. My favorite, however, is using a markup extension and RESX resource files. With this approach, you can have code such as:

<Button Click="OnClick" Content="{my:Resource ResourceName=Resources, ResourceKey=ButtonTitle}"/>

This is very similar to how you’d do it in ASP.NET web forms, and you can even share the resource files. Let’s see the ResourceExtension class, which, by implementing IMarkupExtension<T>, becomes a markup extension, meaning that by convention we can leave out the Extension suffix:

public sealed class ResourceExtension : IMarkupExtension<String>

{

    public String ResourceName { get; set; }

    public String ResourceKey { get; set; }

 

    private Type FindResourceType(params Assembly [] assemblies)

    {

        foreach (var assembly in assemblies.Distinct())

        {

            var resourceType = assembly.GetTypes().SingleOrDefault(x => (x.BaseType == typeof(Object)) && (x.Name == this.ResourceName));

 

            if (resourceType != null)

            {

                return resourceType;

            }

        }

 

        return null;

    }

 

    public String ProvideValue(IServiceProvider serviceProvider)

    {

        var executingAssembly = Assembly.GetExecutingAssembly();

        var callingAssembly = Assembly.GetCallingAssembly();

        var applicationAssembly = Application.Current.GetType().Assembly;

 

        var resourceType = this.FindResourceType(applicationAssembly, callingAssembly, executingAssembly);

 

        if (resourceType != null)

        {

            var resourceManager = new ResourceManager(resourceType);

            return resourceManager.GetString(this.ResourceKey);

        }

        else

        {

            throw new InvalidOperationException(String.Format("Cannot find resource {0} with key {1}.", this.ResourceName, this.ResourceKey));

        }

    }

}

ResourceExtension will try to find the class identified in the ResourceName property in a couple of assemblies and if it finds it, will try to return the resource key from the ResourceKey property; otherwise, it throws an exception, because, obviously, something is wrong.

The resource files need to be compiled as embedded resources:

image

Now, before you can use this, you need to do a couple of things:

  1. Add the NeutralResourcesAssemblyAttribute to your assembly to indicate which language the default resource file refers to (in AssemblyInfo.cs):
    [assembly: NeutralResourcesLanguage("en-US")]

  2. Set the culture of the current thread upon application startup (App.xaml.cs):
    private void Application_Startup(object sender, StartupEventArgs e)

    {

        Thread.CurrentThread.CurrentCulture = new CultureInfo("pt-PT");

        Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;

     

        this.RootVisual = new MainPage();

    }

  3. Add the list of supported languages to the project file (.csproj):
    <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

      <PropertyGroup>

        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

        <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

        <ProductVersion>8.0.50727</ProductVersion>

        <SchemaVersion>2.0</SchemaVersion>

        <ProjectGuid>{4AB634F1-D00D-4461-83F4-3ADA1DF2D47B}</ProjectGuid>

        <ProjectTypeGuids>{A1591282-1198-4647-A2B1-27E5FF5F6F3B};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

        <OutputType>Library</OutputType>

        <AppDesignerFolder>Properties</AppDesignerFolder>

        <SupportedCultures>en-US;pt-PT</SupportedCultures>

        <TargetFrameworkIdentifier>Silverlight</TargetFrameworkIdentifier>

        <TargetFrameworkVersion>v5.0</TargetFrameworkVersion>

        <SilverlightVersion>$(TargetFrameworkVersion)</SilverlightVersion>

        <SilverlightApplication>true</SilverlightApplication>

        <!- ... -->

      </PropertyGroup>

    </Project>

And this is it! Enjoy!

SharePoint Filter Web Part

SharePoint includes a couple of useful web parts that can be used to filter other web parts. The problem is, they are only available in the Enterprise edition. It is easy, however, to build our own filters.

The key interface here is ITransformableFilterValues; it defines the contract for passing one or more values into another web part for the purpose of filtering it. Another useful interface is IDefaultFilterValue, which, guess what, defines a default value to be returned in case no one exists to be returned. Let’s see an example that retrieves values from the query string:

public class QueryStringFilter : WebPart, ITransformableFilterValues, IDefaultFilterValue

{

    public QueryStringFilter()

    {

        this.ChromeType = PartChromeType.None;

        this.Hidden = true;

        this.MultiValueHandling = MultiValueHandling.First;

        this.MultiValueSeparator = String.Empty;

    }

 

    [WebDisplayName("Default Value")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Default Value")]

    [DefaultValue("")]

    [Description("")]

    [Personalizable(PersonalizationScope.Shared)]

    public String DefaultValue { get; set; }

 

    [WebDisplayName("Allow All Value")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Allow All Value")]

    [Personalizable(PersonalizationScope.Shared)]

    [DefaultValue(false)]

    [Description("")]

    public Boolean AllowAllValue { get; set; }

 

    [WebDisplayName("Allow Empty Value")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Allow Empty Value")]

    [Personalizable(PersonalizationScope.Shared)]

    [DefaultValue(false)]

    [Description("")]

    public Boolean AllowEmptyValue { get; set; }

 

    [WebDisplayName("Allow Multiple Values")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Allow Multiple Values")]

    [Personalizable(PersonalizationScope.Shared)]

    [DefaultValue(false)]

    [Description("")]

    public Boolean AllowMultipleValues { get; set; }

 

    [WebDisplayName("Query String Parameter Name")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Query String Parameter Name")]

    [Personalizable(PersonalizationScope.Shared)]

    [DefaultValue("")]

    [Description("")]

    public String ParameterName { get; set; }

 

    [WebDisplayName("Multi Value Handling")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Multi Value Handling")]

    [Personalizable(PersonalizationScope.Shared)]

    [DefaultValue(MultiValueHandling.First)]

    [Description("")]

    public MultiValueHandling MultiValueHandling { get; set; }

 

    [WebDisplayName("Multi Value Separator")]

    [WebPartStorage(Storage.Shared)]

    [WebBrowsable(true)]

    [FriendlyName("Multi Value Separator")]

    [Personalizable(PersonalizationScope.Shared)]

    [DefaultValue("")]

    [Description("")]

    public String MultiValueSeparator { get; set; }

 

    ReadOnlyCollection<String> ITransformableFilterValues.ParameterValues

    {

        get

        {

            var list = new List<String>();

 

            if (String.IsNullOrWhiteSpace(this.ParameterName) == false)

            {

                if (this.AllowMultipleValues == false)

                {

                    list.Add(this.Context.Request.QueryString[this.ParameterName]);

                }

                else

                {

                    var index =

                        Array.IndexOf(

                            this.Context.Request.QueryString.AllKeys.Select(x => (x ?? String.Empty).ToLowerInvariant()).ToArray(),

                            this.ParameterName.ToLowerInvariant());

 

                    if (index >= 0)

                    {

                        if (this.MultiValueHandling == MultiValueHandling.First)

                        {

                            list.Add(this.Context.Request.QueryString.GetValues(index).First());

                        }

                        else if (this.MultiValueHandling == MultiValueHandling.All)

                        {

                            list.AddRange(this.Context.Request.QueryString.GetValues(index));

                        }

                        else

                        {

                            list.Add(String.Join(this.MultiValueSeparator, this.Context.Request.QueryString.GetValues(index)));

                        }

                    }

                }

 

                if (list.Count == 0)

                {

                    if (String.IsNullOrWhiteSpace(this.DefaultValue) == false)

                    {

                        list.Add(this.DefaultValue);

                    }

                    else

                    {

                        if (this.AllowAllValue == false)

                        {

                            if (this.AllowEmptyValue == true)

                            {

                                list.Add(String.Empty);

                            }

                        }

                        else

                        {

                            list.Add(null);

                        }

                    }

                }

            }

 

            return new ReadOnlyCollection<String>(list);

        }

    }

 

    [ConnectionProvider("Query String Filter", "ITransformableFilterValues", AllowsMultipleConnections = true)]

    public ITransformableFilterValues GetFilterValues()

    {

        return this;

    }

}

There are a couple of public properties:

  • ParameterName: the query string key whose value is to be returned;
  • DefaultValue: the default value to be returned, in case the query string does not contain a value for the ParameterName key;
  • AllowAllValue: whether to allow the all (null) value;
  • AllowEmptyValue: whether to allow the empty (“”) value;
  • AllowMultipleValues: whether to allow multiple values or not;
  • MultiValueHandling: what to do if multiple values are found for the ParameterName key: select the first only, return all or combine them all into one;
  • MultiValueSeparator: the separator for combining multiple values.

The web part is invisible and will only show the chrome when the page is in edit mode. After you add it to a page, you can add a connection of type filter to another web part and select the field on the other web part that you want to filter by. The actual

ITransformableFilterValues implementation is returned by the GetFilterValues method, which is marked with the ASP.NET ConnectionProvider attribute so as to make it a connection provider, and can feed several other web parts. The logic inside ParameterValues is a bit tricky because of the AllowAllValue and AllowEmptyValue properties but I think you’ll have no problems following it.

You would normally apply a filter using the browser interface, but you can also do it in markup:

<WebPartPages:SPProxyWebPartManager runat="server">

    <SPWebPartConnections>

        <WebPartPages:SPWebPartConnection ConsumerConnectionPointID="DFWP Filter Consumer ID" ConsumerID="listViewWebPart" ProviderConnectionPointID="ITransformableFilterValues" ProviderID="queryStringFilterWebPart">

            <WebPartPages:TransformableFilterValuesToParametersTransformer ConsumerFieldNames="LinkTitle" ProviderFieldNames="TaskName"/>

        </WebPartPages:SPWebPartConnection>

    </SPWebPartConnections>

</WebPartPages:SPProxyWebPartManager>

In this example, I am binding the LinkTitle field of a list view web part to the TaskName query string parameter provided by the QueryStringFilter web part. If the query string contains a TaskName parameter that matches the list view’s LinkTitle, it will show these records.