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.

SharePoint XSLT Web Part

After my previous post on XSLT processing, what else could follow? Of course, an XSLT web part for SharePoint! Smile

Here I want to solve a couple of problems:

  • Allow the usage of XSLT 2.0;
  • Have a more flexible parameter passing mechanism than <ParameterBindings>;
  • Make the XSLT extension mechanism (parameters, functions) more usable.

Similar to XsltListViewWebPart and the others, this web part will query SharePoint and return the results processed by a XSLT style sheet. I am going to built on top of the classes introduced in the last post. Here is the SPCustomXsltWebPart (please, do give it a better name…):

public enum XsltVersion

{

    Xslt1 = 1,

    Xslt2 = 2

}

 

public class SPCustomXsltWebPart : WebPart, IWebPartTable

{

    private static readonly Regex parametersRegex = new Regex(@"@(\w+)\b", RegexOptions.IgnoreCase);

 

    [NonSerialized]

    private DataTable table;

    [NonSerialized]

    private IOrderedDictionary parameters;

 

    public SPCustomXsltWebPart()

    {

        this.AddDefaultExtensions = true;

        this.RowLimit = Int32.MaxValue;

        this.Parameters = new ParameterCollection();

        this.XsltVersion = XsltVersion.Xslt1;

    }

 

    [Category("XSLT")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("XSL Version")]

    [WebDescription("The XSLT version")]

    [DefaultValue(XsltVersion.Xslt1)]

    public XsltVersion XsltVersion { get; set; }

 

    [Category("XSLT")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("XSL Link")]

    [WebDescription("The URL of a file containing XSLT")]

    [DefaultValue("")]

    public String XslLink { get; set; }

 

    [Category("XSLT")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("XSL")]

    [WebDescription("The XSLT content")]

    [DefaultValue("")]

    [PersistenceMode(PersistenceMode.InnerProperty)]

    public String Xsl { get; set; }

 

    [Category("Query")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("Query")]

    [WebDescription("The CAML query")]

    [DefaultValue("")]

    [PersistenceMode(PersistenceMode.InnerProperty)]

    public String Query { get; set; }

 

    [Category("Query")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("Row Limit")]

    [WebDescription("The row limit")]

    [DefaultValue(Int32.MaxValue)]

    public UInt32 RowLimit { get; set; }

 

    [Category("Query")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("Lists")]

    [WebDescription("The target lists")]

    [DefaultValue("")]

    public String Lists { get; set; }

 

    [Category("Query")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("Webs")]

    [WebDescription("The target webs")]

    [DefaultValue("")]

    public String Webs { get; set; }

 

    [Category("Query")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("View Fields")]

    [WebDescription("The view fields")]

    [DefaultValue("")]

    public String ViewFields { get; set; }

 

    [Category("Query")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("Query Throttle Mode")]

    [WebDescription("The query throttle mode")]

    [DefaultValue(SPQueryThrottleOption.Default)]

    public SPQueryThrottleOption QueryThrottleMode { get; set; }

 

    [Category("General")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("Add Default Extensions")]

    [WebDescription("Adds the default extensions")]

    [DefaultValue(true)]

    public Boolean AddDefaultExtensions { get; set; }

 

    [PersistenceModeAttribute(PersistenceMode.InnerProperty)]

    public ParameterCollection Parameters { get; private set; }

 

    public event EventHandler<XsltExtensionEventArgs> XsltExtension;

 

    protected XsltProvider XsltProvider

    {

        get

        {

            return this.XsltVersion == XsltVersion.Xslt1 ? DefaultXsltProvider.Instance : SaxonXsltProvider.Instance;

        }

    }

    protected virtual void OnXsltExtension(XsltExtensionEventArgs e)

    {

        var handler = this.XsltExtension;

 

        if (handler != null)

        {

            handler(this, e);

        }

    }

 

    protected override void CreateChildControls()

    {

        var xml = this.GetXml();

        var html = this.Render(xml);

        var literal = new LiteralControl(html);

 

        this.Controls.Add(literal);

 

        base.CreateChildControls();

    }

 

    private String GetXslt()

    {

        var xslt = String.Empty;

 

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

        {

            xslt = this.Xsl;

        }

        else if (String.IsNullOrWhiteSpace(this.XslLink) == false)

        {

            var doc = new XmlDocument();

            doc.Load(this.XslLink);

 

            xslt = doc.InnerXml;

        }

 

        return xslt;

    }

 

    private DataTable GetTable()

    {

        if (this.table == null)

        {

            var query = new SPSiteDataQuery();

            query.Query = this.ApplyParameters(this.Query);

            query.QueryThrottleMode = this.QueryThrottleMode;

            query.RowLimit = this.RowLimit;

            query.Lists = this.Lists;

            query.Webs = this.Webs;

            query.ViewFields = this.ViewFields;

 

            this.table = SPContext.Current.Site.RootWeb.GetSiteData(query);

            this.table.TableName = "Row";

 

            foreach (var column in this.table.Columns.OfType<DataColumn>())

            {

                column.ColumnMapping = MappingType.Attribute;

            }

        }

 

        return this.table;

    }

 

    private String ApplyParameters(String value)

    {

        var parameters = this.GetParameters();

 

        value = parametersRegex.Replace(value, x => this.GetFormattedValue(parameters[x.Value.Substring(1)]));

 

        return value;

    }

 

    private String GetFormattedValue(Object value)

    {

        if (value == null)

        {

            return String.Empty;

        }

 

        if (value is Enum)

        {

            return ((Int32)value).ToString();

        }

 

        if (value is DateTime)

        {

            return SPUtility.CreateISO8601DateTimeFromSystemDateTime((DateTime)value);

        }

 

        if (value is IFormattable)

        {

            return (value as IFormattable).ToString(String.Empty, CultureInfo.InvariantCulture);

        }

 

        return value.ToString();

    }

 

    private IOrderedDictionary GetParameters()

    {

        if (this.parameters == null)

        {

            this.parameters = this.Parameters.GetValues(this.Context, this);

        }

 

        return this.parameters;

    }

 

    private String GetXml()

    {

        var sb = new StringBuilder();

        var table = this.GetTable();

 

        using (var writer = new StringWriter(sb))

        {

            table.WriteXml(writer);

        }

 

        sb

            .Replace("<DocumentElement>", "<dsQueryResponse RowLimit='" + this.RowLimit + "'><Rows>")

            .Replace("</DocumentElement>", "</Rows></dsQueryResponse>");

 

        return sb.ToString();

    }

 

    private String ApplyXslt(String xml, String xslt, XsltExtensionEventArgs args)

    {

        return this.XsltProvider.Transform(xml, xslt, args);

    }

 

    private String Render(String xml)

    {

        if (String.IsNullOrWhiteSpace(xml) == true)

        {

            return String.Empty;

        }

 

        var xslt = this.GetXslt();

 

        if (String.IsNullOrWhiteSpace(xslt) == true)

        {

            return String.Empty;

        }

 

        var extensions = new XsltExtensionEventArgs();

 

        this.OnXsltExtension(extensions);

 

        if (this.AddDefaultExtensions == true)

        {

            var defaultExtensions = Activator.CreateInstance(typeof(Microsoft.SharePoint.WebPartPages.DataFormWebPart).Assembly.GetType("Microsoft.SharePoint.WebPartPages.DataFormDdwRuntime"));

            extensions.AddExtension("http://schemas.microsoft.com/WebParts/v2/DataView/runtime", defaultExtensions);

        }

 

        foreach (var ext in extensions.Extensions)

        {

            extensions.AddExtension(ext.Key, ext.Value);

        }

 

        var parameters = this.GetParameters();

 

        foreach (var key in parameters.Keys.OfType<String>())

        {

            extensions.AddParameter(key, String.Empty, parameters[key].ToString());

        }

 

        foreach (var param in extensions.Parameters)

        {

            extensions.AddParameter(param.Name, param.NamespaceUri, param.Parameter.ToString());

        }

 

        return this.ApplyXslt(xml, xslt, extensions);

    }

 

    void IWebPartTable.GetTableData(TableCallback callback)

    {

        callback(this.GetTable().DefaultView);

    }

 

    PropertyDescriptorCollection IWebPartTable.Schema

    {

        get { return TypeDescriptor.GetProperties(this.GetTable().DefaultView); }

    }

}

This class extends the basic WebPart class and adds a couple of properties:

  • XsltVersion: the XSLT version to use, which will result in either my DefaultXsltProvider or the SaxonXsltProvider being used;
  • XslLink: the URL of a file containing XSLT;
  • Xsl: in case you prefer to have the XSLT inline;
  • Query: a CAML query;
  • Webs: the webs to query;
  • Lists: the lists to query;
  • ViewFields: the fields to return;
  • RowLimit: maximum number of rows to return;
  • QueryThrottleMode: the query throttle mode;
  • AddDefaultExtensions: whether to add the default extension functions and parameters;
  • Parameters: a standard collection of ASP.NET parameter controls.

The web part uses SPSiteDataQuery to execute a CAML query. Before the query is executed, any parameters it may have, in the form @ParameterName,  are replaced by actual values evaluated from the Parameters collection. This gives some flexibility to the queries, because, not only ASP.NET includes parameters for all the common sources, it’s very easy to add new ones. The web part knows how to format strings, enumerations, DateTime objects and in general any object implementing IFormattable; if you wish, you can extend it to support other types, but I don’t think it will be necessary.

An example usage:

<web:SPCustomXsltWebPart runat="server" XslLink="~/Style.xslt">

    <Query>

        <Where><Eq><FieldRef Name='Id'/><Value Type='Number'>@Id</Value></Eq></Where>

    </Query>

    <Parameters>

        <asp:QueryStringParameter Name="Id" QueryStringField="Id" Type="Int32" />

    </Parameters>

</web:SPCustomXsltWebPart>

Notice that one of the Xsl or the XslLink properties must be set, and the same goes for the Query.

Hope you find this useful, and let me know how it works!

     

    Querying SharePoint

    Introduction

    SharePoint, being a content management system, of course, offers a couple of ways to query its contents programmatically. Here we will explore some of these options.

    Web Parts

    First, of course, there are the web parts. These allow us to configure queries visually on a page. The most important web parts are:

    ContentByQueryWebPart: use this for simple queries that do not return much contents, on a single site collection. Can be used in SharePoint Online. Can only be customized through XSLT.

    ContentBySearchWebPart (introduced in SharePoint 2013): more powerful, but does not exist in SharePoint Online. Can handle more complex queries that can span multiple site collections and multiple levels of sorting. Can only be customized through HTML and JavaScript templates. Results can be refined.

    XsltListViewWebPart/DataFormWebPart: can be used to view of a specific list. The display can be totally configured. XsltListViewWebPart is a bit more powerful.

    ListViewByQuery: requires that you pass an SPList and a SPQuery instance. Can only display pre-defined views.

    APIs

    There are several APIs for querying, either directly or using the search index.

    SPQuery: can only be applied to a single list:

    var query = new SPQuery();

    query.Query = "<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>Bla Bla</Value></Eq></Where>";

     

    var list = site.RootWeb.Lists["Some List"];

     

    var table = list.GetItems(query).GetDataTable();

    SPSiteDataQuery: can be applied to several lists across a site collection. Has some issues, for example, won’t return values for multi-value fields:

    var query = new SPSiteDataQuery();

    query.Lists = "<List Name='Tasks'/>";

    query.Query = "<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>Bla Bla</Value></Eq></Where>";

    query.Webs = "<Web Scope='SiteCollection'/>";

     

    var table = site.RootWeb.GetSiteData(query);

    KeywordQuery: uses the search index to search for keywords, so it requires that contents are indexed beforehand, and the search service is functioning:

    using (var query = new KeywordQuery(site))

    {

        query.QueryText = "Bla Bla";

        query.ResultsProvider = SearchProvider.Default;

     

        var searchExecutor = new SearchExecutor();

     

        var resultTableCollection = searchExecutor.ExecuteQuery(query);

     

        var searchResult = resultTableCollection.Filter("TableType", KnownTableTypes.RelevantResults).Single();

     

        var table = new DataTable();

        table.TableName = "Result";

        table.Load(searchResult, LoadOption.OverwriteChanges);

    }

    FullTextSqlQuery: uses SharePoint Search SQL to execute queries, which makes it generally more powerful than KeywordQuery:

    using (var query = new FullTextSqlQuery(site))

    {

        query.QueryText = "SELECT * FROM scope() WHERE Title = 'Teste'";

        query.ResultsProvider = SearchProvider.Default;

     

        var searchExecutor = new SearchExecutor();

     

        var resultTableCollection = searchExecutor.ExecuteQuery(query);

     

        var searchResult = resultTableCollection.Filter("TableType", KnownTableTypes.RelevantResults).Single();

     

        var table = new DataTable();

        table.TableName = "Result";

        table.Load(searchResult, LoadOption.OverwriteChanges);

    }

    CrossListQueryInfo and CrossListQueryCache: performs queries in a single site collection but multiple sites, with optional audience targeting. CrossListQueryCache caches the results for a period of time:

    var crossListQueryInfo = new CrossListQueryInfo();

    crossListQueryInfo.Query = "<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>Bla Bla</Value></Eq></Where>";

    crossListQueryInfo.Lists = "<List Name='Some List'/>";

    crossListQueryInfo.Webs = "<Webs Scope='SiteCollection' />";

    crossListQueryInfo.UseCache = true;

     

    var crossListQueryCache = new CrossListQueryCache(crossListQueryInfo);

     

    var table = crossListQueryCache.GetSiteData(site.RootWeb);

    Client KeywordQuery: this is the client counterpart to KeywordQuery. It uses the SharePoint Client Components, or Client Side Object Model (CSOM), which means, it accesses SharePoint through its web services. It is basically similar to the server KeywordQuery, but can be used from a remote machine:

    var keywordQuery = new Microsoft.SharePoint.Client.Search.Query.KeywordQuery(ctx);

    keywordQuery.QueryText = "SharePoint";

     

    var searchExecutor = new Microsoft.SharePoint.Client.Search.Query.SearchExecutor(ctx);

     

    var results = searchExecutor.ExecuteQuery(keywordQuery);

     

    ctx.ExecuteQuery();

     

    var searchResult = results.Value.Where(x => x.TableType == KnownTableTypes.RelevantResults.ToString()).Single();

    Web Services

    Search.asmx: SOAP web service that takes a SharePoint SQL query. Part of the SharePoint Foundation 2010 Web Services, now somewhat obsolete.

    Lists.asmx: Another SOAP web service that can return and update items in a list.

    ListData.svc: WCF Data Service REST/OData that can be used for querying (including OData queries) or modifying contents of lists.

    SharePoint 2013 REST Services: new REST/OData web services introduced in SharePoint 2013. Includes _api/web, _api/search, _api/web/lists, for searches, web or list operations.

    SharePoint Foundation RPC Protocol

    The SharePoint Foundation RPC Protocol, now obsolete, this allowed querying, exporting contents and performing a number of other operations through the OWSSVR.DLL handler. Although almost unused nowadays, still offers the only out of the box way to, for example, export a list in XML format.

    JavaScript

    In SharePoint 2010 the JavaScript Side Object Model (JSOM) was introduced and in version 2013 it was enhanced. It is now possible to do anything that the SharePoint Client API allows.

    Conclusion

    You see, lots of ways to get contents from SharePoint, as usual. Make sure you chose the one that best suits your needs.

    References

    SharePoint 2013 .NET Server, CSOM, JSOM, and REST API index

    When to use the Content Query Web Part or the Content Search Web Part in SharePoint

    Choose the right API set in SharePoint 2013

    Use OData query operations in SharePoint REST requests

    SharePoint Server 2013 Client Components SDK

    SharePoint Search SQL Syntax Reference

    ASP.NET Web Forms Extensibility: Model Binding Value Providers

    ASP.NET 4.5 introduced model binding: basically, it is a way for databound controls – Repeater, GridView, ListView, etc – to be fed, not from a datasource control – ObjectDataSource, EntityDataSource, SqlDataSource, etc -, but from a method in the page. This method needs to return a collection, and may have parameters. The problem is: how these parameters get their values? The answer is: through a model binding value provider.

    A model binding value provider is a class that implements IValueProvider, and normally is injected through a ValueProviderSourceAttribute-derived attribute. ASP.NET includes some implementations:

    If we want, say, to return a value from the Common Service Locator, it’s pretty easy. First, an attribute:

    [Serializable]

    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]

    public sealed class ServiceLocatorAttribute : ValueProviderSourceAttribute

    {

        private readonly Type serviceType;

        private readonly String key;

    
    

        public ServiceLocatorAttribute(Type serviceType, String key = null)

        {

            this.serviceType = serviceType;

            this.key = key;

        }

    
    

        public override IValueProvider GetValueProvider(ModelBindingExecutionContext modelBindingExecutionContext)

        {

            return new ServiceLocatorValueProvider(this.serviceType, this.key);

        }

    }

    And now the actual value provider:

    public sealed class ServiceLocatorValueProvider : IValueProvider

    {

        private readonly Type serviceType;

        private readonly String key;

    
    

        public ServiceLocatorValueProvider(Type serviceType, String key)

        {

            this.serviceType = serviceType;

            this.key = key;

        }

    
    

        public Boolean ContainsPrefix(String prefix)

        {

            return true;

        }

    
    

        public ValueProviderResult GetValue(String key)

        {

            return new ValueProviderResult(ServiceLocator.Current.GetInstance(this.serviceType, this.key), null, CultureInfo.CurrentCulture);

        }

    }

    You can even have the ServiceLocatorAttribute implement the IValueProvider interface, I just separated it because conceptually they are different things.

    Finally, here’s how we would use it:

    public IQueryable<SomeEntity> GetItems([ServiceLocator(typeof(MyComponent), "SomeKey")] MyComponent cmp)

    {

        //do something

    }

    Pretty sleek, don’t you think? Winking smile

    Hosting HTTP Resources

    Introduction

    How do I host thee? Let me count the ways!

    You may not have realized that .NET offers a lot of alternatives when it comes to hosting an HTTP server, that is, without resorting to IIS, IIS Express or the now gone Visual Studio Web Development Server (aka, Cassini, rest in peace); by that, I either mean:

    • Opening up a TCP port and listening for HTTP requests, or a subset of them;
    • Running ASP.NET pages without a server.

    In this post I am going through some of them. Some are specific to web services, but since they understand REST, I think they qualify as well as generic HTTP hosting mechanisms.

    .NET HttpListener

    Let’s start with HttpListener. This is included in .NET since version 2 and offers a decent server for static contents, that is, it cannot run any dynamic contents, like ASP.NET handlers, nor does it know anything about them. You merely point it to a physical folder on your file system, and it will happily serve any contents located inside it. Let’s see an example:

    using (var listener = new System.Net.HttpListener())

    {

        var url = "http://*:2000/";

        listener.Prefixes.Add(url);

        listener.Start();

     

        var ctx = listener.GetContext();

     

        var message = "Hello, World!";

     

        ctx.Response.StatusCode = (Int32) HttpStatusCode.OK;

        ctx.Response.ContentType = "text/plain";

        ctx.Response.ContentLength64 = message.Length;

     

        using (var writer = new StreamWriter(ctx.Response.OutputStream))

        {

            writer.Write(message);

        }

     

        Console.ReadLine();

    }

    This is a very basic example that just listens on port 2000, for any host name and request, and just returns Hello, World! when contacted before shutting down.

    ASP.NET ApplicationHost

    Complementary to HttpListener, we have a way to execute ASP.NET handlers (ASPX pages, ASHX generic handlers and ASMX web services) in a self-hosted application domain. For that, we use the ApplicationHost class to create the ASP.NET application domain, and a regular .NET class for the server implementation. An example:

    public class Host : MarshalByRefObject

    {

        public void ProcessPage(String page, String query, TextWriter writer)

        {

            var worker = new SimpleWorkerRequest(page, query, writer);

            HttpRuntime.ProcessRequest(worker);

        }

    }

     

    //strip out bin\debug, so as to find the base path where web files are located

    var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location).Replace(@"\bin\Debug", String.Empty);

     

    //we need to copy the assembly to the base path

    File.Copy(Assembly.GetExecutingAssembly().Location, Path.Combine(path, "bin", Assembly.GetExecutingAssembly().CodeBase.Split('/').Last()), true);

     

    var host = System.Web.Hosting.ApplicationHost.CreateApplicationHost(typeof(Host), "/", path) as Host;

    host.ProcessPage("Default.aspx", null);

    Notice the File.Copy call; this is necessary because the assembly referenced by the Default.aspx page needs to be located in the same folder as the page. An alternative to this would be to add a post build-event to the Visual Studio project:

    image

    I leave as an exercise to the interested readers how we can combine this with HttpListener! Winking smile

    OWIN WebApp

    Moving on to more recent technologies, we now have OWIN. In case you’ve been living in another world and haven’t heard of OWIN, I’ll just say that it is a standard for decoupling .NET from any particular web servers, like IIS or IIS Express. It also happens to have a self-hosting implementation – which, by the way, uses HttpListener underneath.

    We need to add a reference to the Microsoft.Owin.SelfHost NuGet package:

    image

    After that, we just register an instance of WebApp with the default parameters, add an handler, and we’re done:

    class Program

    {

        public static void Configuration(IAppBuilder app)

        {

            app.Use(new Func<AppFunc, AppFunc>(next => (async ctx =>

            {

                using (var writer = new StreamWriter(ctx["owin.ResponseBody"] as Stream))

                {

                    await writer.WriteAsync("Hello, World!");

                }

            })));

        }

     

        static void Main(String[] args)

        {

            using (WebApp.Start<Program>("http://*:2000"))

            {

                Console.ReadLine();

            }

        }

    }

    Again, no fancy dynamic stuff, just plain and simple HTTP: it waits for a request and just returns Hello, World!. It is possible to run ASP.NET MVC on top of OWIN, that is the goal of project Helios, which is currently in alpha stage. Do check out the Helios NuGet package at https://www.nuget.org/packages/Microsoft.Owin.Host.IIS/1.0.0-alpha1:

    image

    WCF ServiceHost

    Since its release, WCF offers a way for it to be self-hosted in a .NET process. The class responsible for that is ServiceHost, or one of its descendants, like WebServiceHost, more suitable for REST. I will show an example using REST, which can be easily tested using a web browser:

    [ServiceContract]

    public interface IRest

    {

        [WebGet(ResponseFormat = WebMessageFormat.Json)]

        [OperationContract]

        String Index();

    }

     

    public class Rest : IRest

    {

        public String Index()

        {

            return "Hello, World!";

        }

    }

     

    using (var host = new WebServiceHost(typeof(Rest)))

    {

        var url = new Uri(@"http://localhost:2000");

        var binding = new WebHttpBinding();

     

        host.AddServiceEndpoint(typeof(IRest), binding, url);

        host.Open();

     

        Console.ReadLine();

    }

    This example listens for a request of /Index on port 2000 and upon receiving it, returns Hello, World! in JSON format – because we are only sending a string, it will be wrapped in . WCF REST out of the box only supports returning data in XML or JSON format, no Text or HTML, but, to be fair, that’s not what it was meant to. Should be possible to return HTML, but, honestly, it would probably mean more work than it’s worth.

    Web API HttpServer

    Another web services technology in the .NET stack is Web API. Web API uses a concept similar to MVC, with controllers, models and action methods, but no views. It can be self-hosted as well, using the HttpServer class. In order to use it, install the Microsoft.AspNet.WebApi.SelfHost NuGet package. You will notice that its description claims that it is legacy, and has been replaced for another based on OWIN, yet, it is fully functional, if you don’t required it to be OWIN-compliant:

    image

    Because of the Web API architecture, we need to implement a controller for handling requests, :

    public class DummyController : ApiController

    {

        [HttpGet]

        public IHttpActionResult Index()

        {

            return this.Content(HttpStatusCode.OK, "Hello, World!");

        }

    }

    In this example, we do not take any parameters and just return the usual response.

    Here’s the infrastructure code:

    var url = "http://localhost:2000";

    var config = new HttpSelfHostConfiguration(url);

    config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{action}");

     

    using (var server = new HttpSelfHostServer(config))

    {

        server.OpenAsync().Wait();

    }

    The DummyController is found by reflecting the current executing assembly and applying conventions; any HTTP requests for /api/Dummy/Index will land there and the outcome will be plain text.

    IIS Hostable Web Core

    Now, this one is tricky. IIS, from version 7, allows hosting its core engine in-process, that is, from inside another application; this is called IIS Hostable Web Core (HWC). We can supply our own Web.config and ApplicationHost.config files and specify a root folder from which IIS will serve our web resources, including any dynamic contents that IIS can serve (ASPX pages, ASHX handlers, ASMX and WCF web services, etc). Yes, I know, this contradicts my introduction, where I claimed that this post would be about hosting web resources without IIS… still, I think this is important to know, because it can be fully controlled through code.

    You need to make sure HWC is installed… one option is using PowerShell’s Install-WindowsFeature cmdlet:

    Or the Server Manager application:

     

    Features page

     

    Because HWC is controlled through an unmanaged DLL, we have to import its public API control functions and call it with .NET code. Here’s an example:

    public class Host : IDisposable

    {

        private static readonly String FrameworkDirectory = RuntimeEnvironment.GetRuntimeDirectory();

        private static readonly String RootWebConfigPath = Environment.ExpandEnvironmentVariables(Path.Combine(FrameworkDirectory, @"Config\Web.config"));

     

        public Host(String physicalPath, Int32 port)

        {

            this.ApplicationHostConfigurationPath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName() + ".config");

            this.PhysicalPath = physicalPath;

            this.Port = port;

     

            var applicationHostConfigurationContent = File.ReadAllText("ApplicationHost.config");

            var text = String.Format(applicationHostConfigurationContent, this.PhysicalPath, this.Port);

     

            File.WriteAllText(this.ApplicationHostConfigurationPath, text);

        }

     

        ~Host()

        {

            this.Dispose(false);

        }

     

        public String ApplicationHostConfigurationPath

        {

            get;

            private set;

        }

     

        public Int32 Port

        {

            get;

            private set;

        }

     

        public String PhysicalPath

        {

            get;

            private set;

        }

     

        public void Dispose()

        {

            this.Dispose(true);

            GC.SuppressFinalize(this);

        }

     

        protected virtual void Dispose(Boolean disposing)

        {

            this.Stop();

        }

     

        public void Start()

        {

            if (IisHostableWebCoreEngine.IsActivated == false)

            {

                IisHostableWebCoreEngine.Activate(this.ApplicationHostConfigurationPath, RootWebConfigPath, Guid.NewGuid().ToString());

            }

        }

     

        public void Stop()

        {

            if (IisHostableWebCoreEngine.IsActivated == true)

            {

                IisHostableWebCoreEngine.Shutdown(false);

     

                this.PhysicalPath = String.Empty;

                this.Port = 0;

     

                File.Delete(this.ApplicationHostConfigurationPath);

     

                this.ApplicationHostConfigurationPath = String.Empty;

            }

        }

     

        private static class IisHostableWebCoreEngine

        {

            private delegate Int32 FnWebCoreActivate([In, MarshalAs(UnmanagedType.LPWStr)] String appHostConfig, [In, MarshalAs(UnmanagedType.LPWStr)] String rootWebConfig, [In, MarshalAs(UnmanagedType.LPWStr)] String instanceName);

            private delegate Int32 FnWebCoreShutdown(Boolean immediate);

     

            private const String HostableWebCorePath = @"%WinDir%\System32\InetSrv\HWebCore.dll";

            private static readonly IntPtr HostableWebCoreLibrary = LoadLibrary(Environment.ExpandEnvironmentVariables(HostableWebCorePath));

     

            private static readonly IntPtr WebCoreActivateAddress = GetProcAddress(HostableWebCoreLibrary, "WebCoreActivate");

            private static readonly FnWebCoreActivate WebCoreActivate = Marshal.GetDelegateForFunctionPointer(WebCoreActivateAddress, typeof(FnWebCoreActivate)) as FnWebCoreActivate;

     

            private static readonly IntPtr WebCoreShutdownAddress = GetProcAddress(HostableWebCoreLibrary, "WebCoreShutdown");

            private static readonly FnWebCoreShutdown WebCoreShutdown = Marshal.GetDelegateForFunctionPointer(WebCoreShutdownAddress, typeof(FnWebCoreShutdown)) as FnWebCoreShutdown;

     

            internal static Boolean IsActivated

            {

                get;

                private set;

            }

     

            internal static void Activate(String appHostConfig, String rootWebConfig, String instanceName)

            {

                var result = WebCoreActivate(appHostConfig, rootWebConfig, instanceName);

     

                if (result != 0)

                {

                    Marshal.ThrowExceptionForHR(result);

                }

     

                IsActivated = true;

            }

     

            internal static void Shutdown(Boolean immediate)

            {

                if (IsActivated == true)

                {

                    WebCoreShutdown(immediate);

                    IsActivated = false;

                }

            }

     

            [DllImport("Kernel32.dll")]

            private static extern IntPtr LoadLibrary(String dllname);

     

            [DllImport("Kernel32.dll")]

            private static extern IntPtr GetProcAddress(IntPtr hModule, String procname);

        }

    }

    In order for this to work, we need to have an ApplicationHost.config file, a minimum working example being:

    <?xml version="1.0" encoding="UTF-8" ?>

    <configuration>

        <configSections>

            <sectionGroup name="system.applicationHost">

                <section name="applicationPools" />

                <section name="sites" />

            </sectionGroup>

     

            <sectionGroup name="system.webServer">

                <section name="globalModules" />

                <section name="modules" />

                <section name="handlers" />

                <section name="staticContent" />

                <section name="serverRuntime" />

                <sectionGroup name="security">

                    <section name="access"/>

                    <sectionGroup name="authentication">

                        <section name="anonymousAuthentication" />

                        <section name="windowsAuthentication" />

                        <section name="basicAuthentication" />

                    </sectionGroup>

                    <section name="authorization" />

                    <section name="requestFiltering" />

                    <section name="applicationDependencies" />

                    <section name="ipSecurity" />

                </sectionGroup>

                <section name="asp" />

                <section name="caching" />

                <section name="cgi" />

                <section name="defaultDocument" />

                <section name="directoryBrowse" />

                <section name="httpErrors" />

                <section name="httpLogging" />

                <section name="httpProtocol" />

                <section name="httpRedirect" />

                <section name="httpTracing" />

                <section name="isapiFilters" allowDefinition="MachineToApplication" />

                <section name="odbcLogging" />

            </sectionGroup>

        </configSections>

     

        <system.applicationHost>

            <applicationPools>

                <add name="AppPool" managedPipelineMode="Integrated" managedRuntimeVersion="v4.0" autoStart="true" />

            </applicationPools>

     

            <sites>

                <site name="MySite" id="1">

                    <bindings>

                        <binding protocol="http" bindingInformation="*:{1}:localhost" />

                    </bindings>

                    <application path="/" applicationPool="AppPool" >

                        <virtualDirectory path="/" physicalPath="{0}" />

                    </application>

                </site>

            </sites>

        </system.applicationHost>

     

        <system.webServer>

            <globalModules>

                <add name="StaticFileModule" image="%windir%\System32\inetsrv\static.dll" />

                <add name="AnonymousAuthenticationModule" image="%windir%\System32\inetsrv\authanon.dll" />

                <add name="ManagedEngine" image="%windir%\Microsoft.NET\Framework\v4.0.30319\webengine4.dll" />

            </globalModules>

     

            <modules>

                <add name="StaticFileModule" />

                <add name="AnonymousAuthenticationModule" />

                <add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" preCondition="managedHandler" />

                <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" preCondition="managedHandler" />

                <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" preCondition="managedHandler" />

                <add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" preCondition="managedHandler" />

            </modules>

     

            <handlers accessPolicy="Read, Script">

                <add name="PageHandlerFactory-Integrated" path="*.aspx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.PageHandlerFactory" preCondition="integratedMode" />

                <add name="StaticFile" path="*" verb="*" modules="StaticFileModule" resourceType="Either" requireAccess="Read" />

            </handlers>

     

            <staticContent>

                <mimeMap fileExtension=".html" mimeType="text/html" />

                <mimeMap fileExtension=".jpg" mimeType="image/jpeg" />

                <mimeMap fileExtension=".gif" mimeType="image/gif" />

                <mimeMap fileExtension=".png" mimeType="image/png" />

            </staticContent>

        </system.webServer>

    </configuration>

    And all we need to start hosting pages on the port and physical path specified by ApplicationHost.config is:

    using (var host = new Host(path, port))

    {

        host.Start();

     

        Console.ReadLine();

    }

    A couple of notes:

    • Because it calls unmanaged functions, can be terrible to debug;
    • The ApplicationHost.config needs to be in the application’s binary build directory and must have two placeholders, {0} and {1}, for the physical path and HTTP port, respectively;
    • It refers to .NET 4.0, if you want to change it, you will to change a number of modules and paths;
    • Only very few modules are loaded, if you want, get a full file from %HOMEPATH%\Documents\IISExpress\config\ApplicationHost.config and adapt it to your likings.

    .NET TcpListener

    And finally, one for the low-level guys. The TcpListener class allows the opening of TCP/IP ports and the handling of requests coming through them. It doesn’t know anything about the HTTP protocol, of course, so, if we want to leverage it, we need to implement it ourselves. Here’s a very, very, basic example:

    var listener = System.Net.Sockets.TcpListener.Create(2000);

    listener.Start();

     

    using (var client = listener.AcceptTcpClient())

    {

        using (var reader = new StreamReader(client.GetStream()))

        using (var writer = new StreamWriter(client.GetStream()))

        {

            var request = reader.ReadLine();

     

            writer.WriteLine("HTTP/1.1 200 OK");

            writer.WriteLine("Content-type: text/plain");

            writer.WriteLine();

            writer.WriteLine("Hello, World!");

            writer.Flush();

        }

    }

     

    listener.Stop();

    Here we’re just reading any string content and responding with some HTTP headers plus the usual response. Of course, HTTP is quite complex, so I wouldn’t recommend you try to implement it yourself.

    Conclusion

    I presented a couple of solutions for hosting web resources, servicing HTTP requests or running ASP.NET handlers. Hopefully you will find one that matches your needs.

    ASP.NET Web Forms Extensibility: Control Builder Interceptors

    After my previous post on Control Builders, what could possibly come next? Of course, Control Builder Interceptors! Not much documentation on this one, which is a shame, because it is an even more powerful feature that was recently introduced in ASP.NET 4.5.

    A Control Builder Interceptor inherits from, unsurprisingly, ControlBuilderInterceptor. This is configured for the whole application, in the Web.config file, in the compilation section, by a controlBuilderInterceptorType (sorry, no link, since the ASP.NET 4.5 documentation is not online) attribute:

    <compilation targetFramework="4.5" controlBuilderInterceptorType="MyNamespace.MyControlBuilderInterceptor, MyAssembly" />

    Similarly to Control Builders, a Control Builder Interceptor allows us to:

    Granted, less than Control Builders, but the point here is that this is fired for all markup-declared controls, not just those that have a specific Control Builder applied to. With that in mind, we can write code like this:

    public class MyControlBuilderInterceptor : ControlBuilderInterceptor

    {

        //raised for every control on markup

        public static event Action<ControlInterceptedEventArgs> ControlIntercepted;

     

        public override void OnProcessGeneratedCode(ControlBuilder controlBuilder, CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod, IDictionary additionalState)

        {

            var controlDeclaration = buildMethod.Statements[0] as CodeVariableDeclarationStatement;

     

            if (controlDeclaration != null)

            {

                var controlName = controlDeclaration.Name;

     

                buildMethod.Statements.Insert(buildMethod.Statements.Count - 1, new CodeSnippetStatement(String.Concat(this.GetType().FullName, ".Intercept(@", controlName, ");")));

            }

     

            base.OnProcessGeneratedCode(controlBuilder, codeCompileUnit, baseType, derivedType, buildMethod, dataBindingMethod, additionalState);

        }

     

        public override void PreControlBuilderInit(ControlBuilder controlBuilder, TemplateParser parser, ControlBuilder parentBuilder, Type type, String tagName, String id, IDictionary attributes, IDictionary additionalState)

        {

            if ((attributes != null) && (attributes.Contains("Text") == true))

            {

                //make property value uppercase

                attributes["Text"] = (attributes["Text"] as String).ToUpper();

            }

     

            base.PreControlBuilderInit(controlBuilder, parser, parentBuilder, type, tagName, id, attributes, additionalState);

        }

     

        public static void Intercept(Control instance)

        {

            var handler = ControlIntercepted;

     

            if (handler != null)

            {

                handler(new ControlInterceptedEventArgs(instance));

            }

        }

    }

    And there you have it. By adding an event handler to MyControlBuilderInterceptor.ControlIntercepted, we can analyze and change the properties of every control:

    [Serializable]

    public sealed class ControlInterceptedEventArgs : EventArgs

    {

        public ControlInterceptedEventArgs(Control control)

        {

            this.Control = control;

        }

     

        public Control Control { get; private set; }

    }

     

    MyControlBuilderInterceptor.ControlIntercepted += e =>

    {

        var myControl = e.Control as MyControl;

        

        if (myControl != null)

        {

            myControl.Text = myControl.Text.ToUpper();

        }

    };

    Stay tuned for more extensibility points of your favorite framework!

    ASP.NET Web Forms Extensibility: Control Builders

    One of the most often ignored extensibility point in Web Forms is the Control Builder. Control Builders are subclasses of ControlBuilder (or other more specialized, such as FileLevelPageControlBuilder, for pages, or FileLevelMasterPageControlBuilder, for master pages) that can be specified per class. It controls some aspects of a control instance:

    It also allows overriding a couple of things:

    • The parameters specified in the markup (Init);
    • What to do when the control builder is added to a parent control builder (OnAppendToParentBuilder);
    • Modify the code that will be generated in the code-behind class that is produced by ASP.NET or the code that will be used to instantiate the control (ProcessGeneratedCode);
    • Change the tag’s inner textual content (SetTagInnerText);
    • Etc.

    This is a powerful mechanism, which has even been used to allow generic control classes. We apply a control builder through a ControlBuilderAttribute (for regular controls) or FileLevelControlBuilderAttribute for pages, master pages or user controls.

    I won’t go into many details, but instead I will focus on the Init and ProcessGeneratedCode methods.

    Init let’s us do things such as:

    public override void Init(TemplateParser parser, ControlBuilder parentBuilder, Type type, String tagName, String id, IDictionary attribs)

    {

        if (type == typeof(SomeBaseControl)

        {

            //replace the control's type for another one

            type = typeof(SomeDerivedControl);

     

            //convert an hypothetical Text property value to upper case

            attribs["Text"] = (attribs["Text"] as String).ToUpper();

        }

     

        base.Init(parser, parentBuilder, type, tagName, id, attribs);

    }

    And ProcessGeneratedCode, messing with the generated page class:

    public override void ProcessGeneratedCode(CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod)

    {

        //add some interface to the generated page class

        derivedType.BaseTypes.Add(typeof(ISomeInterface));

     

        //add a property implementation to the generated page class

        var prop = new CodeMemberProperty();

        prop.Attributes = MemberAttributes.Public;

        prop.Name = "SomeProperty";

        prop.Type = new CodeTypeReference(typeof(String));    

        prop.GetStatements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression("Hello, World, from a generated property!")));

        

        derivedType.Members.Add(prop);

     

        base.ProcessGeneratedCode(codeCompileUnit, baseType, derivedType, buildMethod, dataBindingMethod);

    }

    But also something MUCH more fun! Imagine you are using an IoC container – I will use Unity, but you can use whatever you want. We might have something like this in Application_Start (or whatever method spawned from it);

    var unity = new UnityContainer();

    unity.RegisterInstance<MyControl>(new MyControl { Text = "Bla bla" });

    ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(unity));

    Notice I am using the Common Service Locator to abstract the IoC container and to make the code independent of it. Here, I am assigning a static instance to the MyControl type, in essence, a singleton.

    Now, we can change our control builder so as to have the control build method return this instance:

    public override void ProcessGeneratedCode(CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod)

    {

        //return ServiceLocator.Current.GetInstance(typeof(MyControl));

        var type = Type.GetType((buildMethod.Statements[0] as CodeVariableDeclarationStatement).Type.BaseType);

        var currentProperty = new CodePropertyReferenceExpression(new CodeTypeReferenceExpression(typeof (ServiceLocator)), "Current");

        var getInstance = new CodeMethodInvokeExpression(currentProperty, "GetInstance", new CodeTypeOfExpression(type));

        var @cast = new CodeCastExpression(type, getInstance);

        var @return = new CodeMethodReturnStatement(@cast);

     

        buildMethod.Statements.Clear();

        buildMethod.Statements.Add(@return);

     

        base.ProcessGeneratedCode(codeCompileUnit, baseType, derivedType, buildMethod, dataBindingMethod);

    }

    In case you didn’t notice, what this does is, every time the MyControl control is instantiated in a page, for every request, ASP.NET will always return the same instance!

    Now, I am not saying that you SHOULD do this, but only that you CAN do this! Winking smile

    Take care out there…

    ASP.NET Web Forms Prompt Validator

    For those still using Web Forms and Microsoft’s validation framework, like yours truly – and I know you’re out there! -, it is very easy to implement custom validation by leveraging the CustomValidator control. It allows us to specify both a client-side validation JavaScript function and a server-side validation event handler.

    In the past, I had to ask for confirmation before a form was actually submitted; the native way to ask for confirmation is through the browser’s confirm function, which basically displays a user-supplied message and two buttons, OK and Cancel. I wrapped it in a custom reusable validation control, which I am providing here:

       1: [DefaultProperty("PromptMessage")]

       2: public sealed class PromptValidator : CustomValidator

       3: {

       4:     [DefaultValue("")]

       5:     public String PromptMessage { get; set; }

       6:  

       7:     protected override void OnPreRender(EventArgs e)

       8:     {

       9:         var message = String.Concat("\"", this.PromptMessage, "\"");

      10:  

      11:         if ((this.PromptMessage.Contains("{0}") == true) && (this.ControlToValidate != String.Empty))

      12:         {

      13:             message = String.Concat("String.format(\"", this.PromptMessage, "\", args.Value)");

      14:         }

      15:  

      16:         this.ClientValidationFunction = String.Concat("new Function('sender', 'args', 'args.IsValid = confirm(", message, ")')");

      17:         this.EnableClientScript = true;

      18:  

      19:         base.OnPreRender(e);

      20:     }

      21: }

    A sample usage without any target control might be:

       1: <web:PromptValidator runat="server" PromptMessage="Do you want to submit your data?" ErrorMessage="!"/>

    And if you want to specifically validate a control’s value:

       1: <web:PromptValidator runat="server" PromptMessage="Do you want to accept {0}?" ErrorMessage="!" ControlToValidate="text" ValidateEmptyText="true"/>

    When submitting your form, you will get a confirmation prompt similar to this (Chrome):

    image

    Generating GDI+ Images for the Web

    .NET’s Graphics Device Interface (GDI+) is Microsoft’s .NET wrapper around the native Win32 graphics API. It is used in Windows desktop applications to generate and manipulate images and graphical contexts, like those of Windows controls. It works through a set of operations like DrawString, DrawRectangle, etc, exposed by a Graphics instance, representing a graphical context and it is well known by advanced component developers. Alas, it is rarely used in web applications, because these mainly consist of HTML, but it is possible to use them. Let’s see how.

    Let’s start by implementing a custom server-side control inheriting from Image:

       1: public class ServerImage: Image

       2: {

       3:     private System.Drawing.Image image;

       4:

       5:     public ServerImage()

       6:     {

       7:         this.ImageFormat = ImageFormat.Png;

       8:         this.CompositingQuality = CompositingQuality.HighQuality;

       9:         this.InterpolationMode = InterpolationMode.HighQualityBicubic;

      10:         this.Quality = 100L;

      11:         this.SmoothingMode = SmoothingMode.HighQuality;

      12:     }

      13:

      14:     public Graphics Graphics { get; private set; }

      15:

      16:     [DefaultValue(typeof(ImageFormat), "Png")]

      17:     public ImageFormat ImageFormat { get; set; }

      18:

      19:     [DefaultValue(100L)]

      20:     public Int64 Quality { get; set; }

      21:

      22:     [DefaultValue(CompositingQuality.HighQuality)]

      23:     public CompositingQuality CompositingQuality { get; set; }

      24:

      25:     [DefaultValue(InterpolationMode.HighQualityBicubic)]

      26:     public InterpolationMode InterpolationMode { get; set; }

      27:

      28:     [DefaultValue(SmoothingMode.HighQuality)]

      29:     public SmoothingMode SmoothingMode { get; set; }

      30:

      31:     protected override void OnInit(EventArgs e)

      32:     {

      33:         if ((this.Width == Unit.Empty) || (this.Height == Unit.Empty) || (this.Width.Value == 0) || (this.Height.Value == 0))

      34:         {

      35:             throw (new InvalidOperationException("Width or height are invalid."));

      36:         }

      37:

      38:         this.image = new Bitmap((Int32)this.Width.Value, (Int32)this.Height.Value);

      39:         this.Graphics = System.Drawing.Graphics.FromImage(this.image);

      40:         this.Graphics.CompositingQuality = this.CompositingQuality;

      41:         this.Graphics.InterpolationMode = this.InterpolationMode;

      42:         this.Graphics.SmoothingMode = this.SmoothingMode;

      43:

      44:         base.OnInit(e);

      45:     }

      46:

      47:     protected override void Render(HtmlTextWriter writer)

      48:     {

      49:         var builder = new StringBuilder();

      50:

      51:         using (var stream = new MemoryStream())

      52:         {

      53:             var codec = ImageCodecInfo.GetImageEncoders().Single(x => x.FormatID == this.ImageFormat.Guid);

      54:

      55:             var parameters = new EncoderParameters(1);

      56:             parameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, this.Quality);

      57:

      58:             this.image.Save(stream, codec, parameters);

      59:

      60:             builder.AppendFormat("data:image/{0};base64,{1}", this.ImageFormat.ToString().ToLower(), Convert.ToBase64String(stream.ToArray()));

      61:         }

      62:

      63:         this.ImageUrl = builder.ToString();

      64:

      65:         base.Render(writer);

      66:     }

      67:

      68:     public override void Dispose()

      69:     {

      70:         this.Graphics.Dispose();

      71:         this.Graphics = null;

      72:

      73:         this.image.Dispose();

      74:         this.image = null;

      75:

      76:         base.Dispose();

      77:     }

      78: }

    Basically, this control discards the ImageUrl property and replaces it with a Data URI value generated from a stored context. You need to define the image’s Width and Height and you can also optionally specify other settings such as the image’s quality percentage (Quality), compositing quality (CompositingQuality), interpolation (InterpolationMode) and smoothing modes (SmootingMode). These settings can be used to improve the outputted image quality.

    Finally, you use it like this. First, declare a ServerImage control on your page:

       1: <web:ServerImage runat="server" ID="image" Width="200px" Height="100px"/>

    And then draw on its Context like you would in a Windows application:

       1: protected override void OnLoad(EventArgs e)

       2: {

       3:     this.image.Graphics.DrawString("Hello, World!", new Font("Verdana", 20, FontStyle.Regular, GraphicsUnit.Pixel), new SolidBrush(Color.Blue), 0, 0);

       4:

       5:     base.OnLoad(e);

       6: }

    Basically, this control discards the ImageUrl property and replaces it with a Data URI value generated from a stored context. You need to define the image’s Width and Height and you can also optionally specify other settings such as the image’s quality percentage (Quality), compositing quality (CompositingQuality), interpolation (InterpolationMode) and smoothing modes (SmootingMode). These settings can be used to improve the outputted image quality.

    Finally, you use it like this. First, declare a ServerImage control on your page:

       1: <web:ServerImage runat="server" ID="image" Width="200px" Height="100px"/>

    And then draw on its Context like you would in a Windows application:

       1: protected override void OnLoad(EventArgs e)

       2: {

       3:     this.image.Graphics.DrawString("Hello, World!", new Font("Verdana", 20, FontStyle.Regular, GraphicsUnit.Pixel), new SolidBrush(Color.Blue), 0, 0);

       4:

       5:     base.OnLoad(e);

       6: }

    The result is this IMG tag with a Data URI content, that you can save or copy to the clipboard:

    image

    Pretty sleek, don’t you think? Winking smile

    Conditional Content in SharePoint Markup

    In SharePoint, there are several web parts that allow us to have different contents depending on some conditions:

    • LoginView (ASP.NET): allows the definition of templates per authenticated or anonymous user:
    <asp:LoginView runat="server">

        <AnonymousTemplate>

            <!-- anonymous content -->

        </AnonymousTemplate>

        <LoggedInTemplate>

            <!-- authenticated content -->

        </LoggedInTemplate>

        <RoleGroups>

            <asp:RoleGroup Roles="Admin">

                <ContentTemplate>

                    <!-- admin content -->

                </ContentTemplate>

            </asp:RoleGroup>

        </RoleGroups>

    </asp:LoginView>

    <SharePoint:SPSecurityTrimmedControl runat="server" PermissionMode="All" PermissionContext="CurrentSite" Permissions="ManageWeb">

        <!-- secure content -->

    </SharePoint:SPSecurityTrimmedControl>

    • EditModePanel: for displaying contents in a web part page depending on its edit mode:
    <PublishingWebControls:EditModePanel runat="server" PageDisplayMode="Edit">

        <!-- edit content -->

    </PublishingWebControls:EditModePanel>

    <SharePoint:UIVersionedContent runat="server" UIVersion="4">

        <ContentTemplate>

            <!-- content for SharePoint 2010 -->

            <!-- no code is run if does not match UIVersion -->

        </ContentTemplate>

    </SharePoint:UIVersionedContent>

     

     

    <SharePoint:VersionedPlaceholder runat="server" UIVersion="4">

        <!-- content for SharePoint 2010 -->

        <!-- code is always run but not rendered if does not match UIVersion -->

    </SharePoint:VersionedPlaceholder>

    • AuthoringContainer: displays content depending on whether the current user has write or read rights on the current page or if it has an associated list item:
    <PublishingWebControls:AuthoringContainer runat="server" DisplayAudience="ReadersOnly">

        <!-- content for readers -->

    </PublishingWebControls:AuthoringContainer>

    <PublishingWebControls:DeviceChannelPanel runat="server" IncludedChannels="iPhone">

        <!-- content for iPhones -->

    </PublishingWebControls:DeviceChannelPanel>

    • DataViewWebPart: allows the passing of parameters and the usage of XSL for rendering logic;

    I imagine you are now rolling your eyes: DataViewWebPart? how come? Well, because it doesn’t need to point to a specific list or view (unlike XsltListViewWebPart), it is very useful for markup-based customizations that will only depend on parameters:

       1: <WebPartPages:DataFormWebPart runat="server" Title="Conditional Content">

       2:     <ParameterBindings>

       3:         <ParameterBinding Name="MyParameter" Location="QueryString(MyParameter)"/>

       4:     </ParameterBindings>

       5:     <XSL>

       6:         <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl asp" xmlns:asp="System.Web.UI.WebControls">                                                  

       7:             <xsl:param name="MyParameter"/>                                                                                                                                        

       8:             <xsl:template match="/">

       9:                 <asp:Label runat="server" Text="Some content, just to show ASP.NET controls inside a SharePoint DataFormWebPart"/>

      10:                 <xsl:choose>

      11:                     <xsl:when test="$MyParameter=''">

      12:                         No parameter...

      13:                     </xsl:when>

      14:                     <xsl:otherwise>

      15:                         Allright! <xsl:value-of select="$MyParameter"/>

      16:                     </xsl:otherwise>

      17:                 </xsl:choose>                                                                                                

      18:             </xsl:template>

      19:         </xsl:stylesheet>

      20:     </XSL>                                        

      21: </WebPartPages:DataFormWebPart>

    You can use this technique for:

    • Including scripts and stylesheets;
    • Including server-side controls.

    It’s just a matter of rolling out some XSL to the rescue!

    You may be already familiar with the available parameters, but you can find the full list here: http://msdn.microsoft.com/en-us/library/office/ff630170(v=office.15).aspx.

    Another SharePoint Designer-only solution that may come in handy! 😉