left-icon

Getting the Most from LINQPad Succinctly®
by José Roberto Olivas Mendoza

Previous
Chapter

of
A
A
A

CHAPTER 4

LINQPad Extensibility

LINQPad Extensibility


LINQPad provides elements to extend its power by letting you write your own classes or methods in order to improve several features included in the product. This chapter will focus on writing custom extensions and visualizers.

Writing custom extensions

LINQPad allows you to write your own methods in order to make them available for all queries. This is done by using the My Extensions node tab, which is located beside the My Queries tree view in the user interface.

My Extensions Node and Its Default Code

Figure 23: My Extensions Node and Its Default Code

The first time you click on the My Extensions node, an empty code template is built automatically by LINQPad. This empty code template can also be activated using Ctrl+Shift+Y. As noted in Figure 23, this is a C# Program query that contains a Main method and an empty definition for a static class named MyExtensions. All custom extension methods should be coded within this class, and when they’re ready, you should press F5 or click the Execute Query button to make LINQPad compile the query. This compilation will create a custom assembly called MyExtensions.FW40.dll in the LINQPad Plugins folder, and make all methods available for all queries.

String and DateTime extensions sample

The first example of custom extensions will expose a series of methods to manage string and DateTime value types.

Code Listing 23: An Example of Custom Extensions

void Main()

{

     // Write code to test your extensions here. Press F5 to compile and run.

}

public static class MyExtensions

{

        public static string Proper(this string inputString)

        {

            if (inputString == string.Empty) return string.Empty;

            if (inputString.Contains(" "))

            {

                var result = string.Empty;

                var wordsList = inputString.Split(' ');

                result = wordsList.Aggregate(result, (current, word) => current + (word.Substring(0, 1).ToUpper() + word.Substring(1).ToLower() + " "));

                return result.Trim();

            }

            return inputString.Substring(0, 1).ToUpper() + inputString.Substring(1).ToLower();

        }

     // Write custom extension methods here. They will be available to all queries.

     public static DateTime BackNDays(this DateTime currentDateTime, int daysToGoBack)

        {

            return (currentDateTime.Subtract(TimeSpan.FromDays(daysToGoBack)));

        }

     public static DateTime ForwardNDays(this DateTime currentDateTime, int daysToGoBack)

        {

            return (currentDateTime.Add(TimeSpan.FromDays(daysToGoBack)));

        }

        public static string ToXmlSchemaDateTime(this DateTime currentDateTime)

        {

            var xmlschemaDatetime = XmlConvert.ToString(DateTime.Now, XmlDateTimeSerializationMode.Unspecified);

            xmlschemaDatetime = xmlschemaDatetime.Substring(0, xmlschemaDatetime.IndexOf(".", StringComparison.Ordinal));

            return (xmlschemaDatetime);

        }

}

// You can also define non-static classes, enums, etc.

This code exposes four extension methods:

  • Proper: Converts the first letter of every word found in the string, passed as a parameter, to an uppercase letter, and the rest of that word to lowercase.
  • BackNDays: Returns the DateTime value that results from subtracting the number of days, passed as an integer parameter, from the DateTime value, also passed as a parameter.
  • ForwardNDays: Returns the DateTime value that results from adding the number of days, passed as an integer parameter, to the DateTime value, also passed as a parameter.
  • ToXmlSchemaDateTime: Returns a string representing the DateTime value, passed as a parameter, according to the XML Schema DateTime value definition.

The following query tests the extension methods written in the previous code.

Code Listing 24: Testing Custom Extensions

var properName = "THIS IS A PROPER NAME";

properName.Proper().Dump();

DateTime.Now.BackNDays(3).Dump();

DateTime.Now.ForwardNDays(5).Dump();

DateTime.Now.ToXmlSchemaDateTime().Dump();

The output displayed by this query is shown in the following figure.

Testing the Custom Extension Methods

Figure 24: Testing the Custom Extension Methods

Enumerating the properties and values for an object

Another useful application for extension methods is an object-properties enumerator, including their values. To do this, add the following method to the MyExtensions class.

Code Listing 25: Enumerating Properties and Values for an Object

public static Dictionary<string, object> Properties(this object myself)

{

  return myself.GetType().GetProperties().Where(prop => prop.CanRead).ToDictionary(prop => prop.Name, prop => prop.GetValue(myself));

}

The first thing this code does is retrieve the type of the object passed as a parameter with the GetType method. Then, the GetProperties method returns a PropertyInfo array with all properties belonging to the object, including their values. The LINQ Where method is employed to choose those properties with a Get accessor available (CanRead equals to true). Then, the ToDictionary method transforms those properties in a <string, object> collection, where the name of each property is used as a key, and each property value corresponds to each dictionary item’s value.

The following query will be used to test this extension method.

Code Listing 26: Testing Enumeration of Properties

void Main()

{

     var custombutton = new CustomButton();

     var properties = custombutton.Properties();

     properties.Dump();

}

// Define other methods and classes here

public class CustomButton : System.Windows.Forms.Button

{

    public CustomButton()

     {

         Text = "Custom Button Class";

         Font = new System.Drawing.Font("Segoe UI",12);

     }

}

As noted in the previous code, the query doesn’t display the button itself, but its properties. A partial view of the output is shown in the following figure.

Displaying the Properties for an Object

Figure 25: Displaying the Properties for an Object

Packaging custom extension methods as a plugin

It’s possible to package custom extension methods as a plugin for LINQPad. This is done by creating a class library project in Visual Studio and adding a reference to LINQPad.exe into that project. Once the project is built, you should copy the assembly DLL and any dependencies into LINQPad’s plugins folder (usually My Documents\LINQPad Plugins).

For the purposes of this book, a project called Linqpadextensions will be created, and all the extension methods explained in the previous sections of this chapter will be included.

Linqpadextensions Project in Visual Studio’s Solution Explorer

Figure 26: Linqpadextensions Project in Visual Studio’s Solution Explorer

As highlighted in the previous figure, a custom class named MyExtensions is defined in a program called MyExtensions.cs. The code for this program is as follows.

Code Listing 27: MyExtensions.cs Program Listing

using System;

using System.Collections.Generic;

using LINQPad.datetimevalues;

using LINQPad.objectshandling;

using LINQPad.stringsmanager;

namespace LINQPad

{

    public static class MyExtensions

    {

        public static string ToXmlSchemaDateTime(this DateTime currentDateTime)

        {

            return new DateTimeManager().ToXmlSchemaDateTime(currentDateTime);

        }

        public static DateTime BackNDays(this DateTime inputDateTime, int daysNumber)

        {

            return (new DateTimeManager().BackNDays(inputDateTime, daysNumber));

        }

        public static DateTime ForwardNDays(this DateTime inputDateTime, int daysNumber)

        {

            return (new DateTimeManager().ForwardNDays(inputDateTime, daysNumber));

        }

        public static string Proper(this string inputString)

        {

            return (Strings.Proper(inputString));

        }

        public static Dictionary<string, object> Properties(this object mySelf)

        {

            return ObjectsManager.Properties(mySelf);

        }

    }

}

The most important thing about this code is the namespace declaration. As noted in the program, the default namespace is declared as LINQPad. This ensures that all classes defined in the custom assembly will be available when queries are executed, if the DLL is copied into LINQPad’s plugins folder.

Also, for maintenance reasons, the actual methods that return the results are defined in separate namespaces and classes. These methods are called from the MyExtensions class.

Note: The explanation for all programs that are part of the Linqpadextensions project are beyond the scope of this book. The source code for the project is supplied as part of the examples available at GitHub.

Creating a custom visualizer

LINQPad can display the output generated by a query in two ways. The first and most common way is calling the Dump extension method, which creates an instance of the DumpContainer object for displaying the results. The second way is by using the DisplayControl and DisplayWpfElement methods of the PanelManager static class, which creates an instance of an OutputPanel object and returns it to the calling process.

Both the PanelManager static class and the DumpContainer object can be accessed from LINQPad queries, and can be used in custom extension methods declared in MyExtensions or packed into a custom assembly. This means that you can customize them to meet a particular need. In the case of custom assembly packaging, a reference to LINQPad.exe must be added to the project in order to gain access to the PanelManager static class and the DumpContainer object.

The following sections will explain some ways to use the PanelManager static class and the DumpContainer object.

Displaying rows from a LINQ to SQL entity in a grid view

The DisplayControl method of the PanelManager class takes a Windows Forms control and shows it in an output panel. The method accepts an optional string parameter to identify the instance of the output panel created. The string is also displayed in the Results panel.

Because the DisplayControl method can take any Windows Forms control, it’s possible to show the contents of a data table using a DataGridView. An extension method can be written to feed the DataGridView control with the results of a LINQ to SQL query.

Writing the extension method

The following extension method assigns the contents of a DataTable into a DataGridView, and displays the control using a custom OutputPanel.

Code Listing 28: Using a custom OutputPanel to display a DataTable

public static void DisplayInGrid(this DataTable dataTable,string title = null)

 {

     if (string.IsNullOrEmpty(title)) title = "&Custom";

     var dgrid = new DataGridView { DataSource = dataTable };

     PanelManager.DisplayControl(dgrid, title);

 }

The first line within the braces sets the title variable to &Custom if no string value is passed when the method is invoked. Then, the second line creates a DataGridView instance and places it into a variable called dgrid. The DataSource property takes the contents of the dataTable parameter to fill the DataGridView. Finally, the method calls DisplayControl and passes the values of the dgrid and title variables.

Solving the IQueryable<T> issue

At this point, everything seems easy with the previous extension method. But when the following query is executed, LINQPad throws an exception.

Code Listing 29: Displaying Data in a Custom OutputPanel

var states = States.Select(row => new {row.State_id,row.State_name, row.State_abbr});

states.DisplayInGrid("Custom Panel");

The DisplayInGrid Extension Method Fails

Figure 27: The DisplayInGrid Extension Method Fails

The exception displayed in Figure 27 is fired because a LINQ to SQL query returns an IQueryable<T> object, and the extension method is built to be used by a DataTable object.

To solve this, an extension method for an IQueryable<T> object and an auxiliary method should be implemented in order to make a DataTable object available for the DisplayInGrid method.

Code Listing 30: Transforming IQueryable<T> to a DataTable

public static DataTable LinqToDataTable<T>(this IQueryable<T> queryData)

 {

   return (EnumToDataTable(queryData.AsEnumerable()));

 }

private static DataTable EnumToDataTable<T>(IEnumerable<T> queryData)

 {

            var result = new DataTable();

            if (queryData == null) return result;

            PropertyInfo[] queryDataProps = null;

            foreach (var queryRecord in queryData)

            {

                if (queryDataProps == null)

                {

                    queryDataProps = queryRecord.ReadProperties();

                    foreach (var property in queryDataProps)

                    {

                        var columnType = property.PropertyType;

                        if (columnType.IsGenericType && (columnType.GetGenericTypeDefinition() == typeof(Nullable<>)))

                        {

                            columnType = columnType.GetGenericArguments()[0];

                        }

                        result.Columns.Add(new DataColumn(property.Name, columnType));

                    }

                }

                var resultRow = result.NewRow();

                foreach (var property in queryDataProps)

                {

                    resultRow[property.Name] = property.GetValue(queryRecord, null) ?? DBNull.Value;

                }

                result.Rows.Add(resultRow);

            }

  return (result);

}

The LinqToDataTable method shown in Code Listing 30 is the actual extension method. This method returns a DataTable object from an IQueryable<T> object. To perform this conversion, the method calls a private method named EnumToDataTable, passing to it the IQueryable<T> object as an enumeration (the AsEnumerable method is used for this purpose).

The EnumToDataTable method creates a DataTable object named result. The columns for the DataTable are created using the property name and the property type of each property belonging to the first enumeration item. The process is performed this way because these properties are the same for all items in the enumeration. After that, the method creates a DataRow object called resultRow for each item in the enumeration, and copies each value contained in the item by iterating its properties, and passing the value of each one to the corresponding column in the DataRow object. Each column name in the DataRow object matches the name of each property of the enumeration item.

Finally, the DataRow object is added to the DataTable, and this is returned to the calling process after the loop ends.

Getting the expected results

Now, by making a few changes in the query…

Code Listing 31: Using LinqToDataTable() Extension Method

var states = States.Select(row => new {row.State_id,row.State_name, row.State_abbr}).LinqToDataTable();

states.DisplayInGrid("Custom Panel");

…the following output is displayed:

The DisplayInGrid Extension Method Working as Expected

Figure 28: The DisplayInGrid Extension Method Working as Expected

The desired DataViewGrid control is displayed in a custom output panel, as seen in Figure 28. This is the result expected from the DisplayInGrid extension method. The highlighted portion shows the title for the OutputPanel instance created.

Overloading the DisplayInGrid extension method

The solution given to the IQueryable<T> issue might not be suitable for practical usages. Sometimes, the user can forget to employ the LinqToDataTable extension method before calling DisplayInGrid, causing LINQPad to throw the IQueryable<T> exception and crashing the query.

The problem can be solved in a better way by overloading the DisplayInGrid extension method, allowing an IQueryable<T> object as a parameter.

Code Listing 32: Overloading DisplayInGrid to Avoid LinqToDataTable()

public static void DisplayInGrid<T>(this IQueryable<T> queryData,string title = null)

 {

    if (string.IsNullOrEmpty(title)) title = "&Custom";

    DisplayInGrid(EnumToDataTable(queryData.AsEnumerable()),title);

 }

As noted in this code, the overloaded DisplayInGrid method turns out as a generic method. This is required because the IQueryable object passed to the method can be the result of any LINQ to SQL query, and the type for this result is unknown at the time of the assembly development. At the end, the overloaded method employs the DataTable version of itself. To pass the IQueryable<T> object as a DataTable, it uses the EnumToDataTable method, which returns a DataTable object from an IQueryable<T> object.

Now, if the query is written in this way…

Code Listing 33: The Original DisplayInGrid Example

var states = States.Select(row => new {row.State_id,row.State_name, row.State_abbr});

states.DisplayInGrid("Custom Panel");

…which is the original query that caused LINQPad to throw the exception, the same result for the DisplayInGrid method will be shown as expected.

Stacking WPF elements

Multiple elements can be displayed on a single output panel. This can be done by using the StackWpfElement method of the PanelManager class. The method can be invoked from any LINQPad query, or from a custom extension method for that purpose.

Creating the extension method

To create an extension method for WPF elements, add the following code to the Linqpadextensions project.

Code Listing 34: StackWpfControl Extension Method

public static void StackWpfControl(this System.Windows.UIElement element, string title = null)

  {

     if (string.IsNullOrEmpty(title)) title = "&Custom";

     PanelManager.StackWpfElement(element, title);

  }

The extension method shown in this code receives a UIElement object, and a string to be displayed as a title for the output panel. This method can be used with any WPF element.

The following query…

Code Listing 35: Testing StackWpfControl

var button = new System.Windows.Controls.Button {Content = "My Button"};

button.StackWpfControl();

…produces this output:

A WPF Element Displayed in an OutputPanel.

Figure 29: A WPF Element Displayed in an OutputPanel.

Stacking several elements at once

The StackWpfControl extension method can be called repeatedly to stack several WPF elements in an output panel. However, it could be created as an extension method that stacks several objects at once.

Code Listing 36: Stacking Several Controls at Once

public static void StackWpfControls(this List<System.Windows.UIElement> elements, string title = null)

  {

            elements.ForEach(element => StackWpfControl(element,title));

  }

The previous code defines an extension method that receives a List of System.Windows.UIElement objects, and a string to be used as a title for the output panel. The ForEach LINQ method iterates the list and calls the StackWpfControl method for each one of its elements.

Now, this query…

Code Listing 37: Testing StackWpfControls

List<System.Windows.UIElement> elements = new List<System.Windows.UIElement> {new System.Windows.Controls.Button {Content = "My Button"},  new System.Windows.Controls.Label {Content = "My Label"}, new System.Windows.Controls.Expander {Header = "More"}};

elements.StackWpfControls();

…displays the following output:

Several WPF Elements Displayed at Once

Figure 30: Several WPF Elements Displayed at Once

Catching element events

Events associated to WPF elements can be caught in LINQPad queries. The following code uses the Click event of a WPF button to close the OutputPanel that displays the button.

Code Listing 38: Trapping Element Events

void Main()

{

     var button = new System.Windows.Controls.Button {Content = "My Button"};

     button.Click += catchedEvent;

     button.StackWpfControl("Events");

}

private static void catchedEvent(object sender, System.EventArgs args)

{

        PanelManager.GetOutputPanel("Events").Close();

}

// Define other methods and classes here

When the previous query is executed, an output panel named Events will be displayed with the WPF button in it. The button will respond to the Click event after the query has finished executing. Clicking on the button will close the Events output panel.

Note: A reference to PresentationCore.dll, PresentationFramework.dll, System.Xaml.dll, and WindowsBase.dll must be added to the queries and to the Linqpadextensions project in order to get the expected results from all WPF elements samples.

The DumpContainer object

The common way query results are displayed is by employing the Dump extension method. Every time Dump is invoked, a DumpContainer object is created to display query results. This object has some properties that can be used to control the mode in which results are shown. The following table summarizes these properties.

Table 2: DumpContainer Properties

Property

Description

Content

An object instance that contains the results to be displayed.

DumpDepth

Establishes the maximum number of levels Dump will display, in case of a query result with multiple nested levels. The default value is 5.

Style

A string that tells DumpContainer how to display the results. This string is expressed in CSS.

The DumpContainer object presents the results of a query by using its own Dump method, which can take two optional parameters:

  • Description (string): Displays a description at the top of the results.
  • ToDataGrid (Boolean): If a value of true is passed using this parameter, the results will be displayed in a data grid instead of Rich Text Format. The default value for this parameter is false.

Creating a custom Dump method

A custom Dump method can be created using the following code.

Code Listing 39: Custom Dump Method

public static void MyDump(this object content, string title = null, string style = null,bool toDataGrid = false)

  {

      if (string.IsNullOrEmpty(title)) title = string.Empty;

      if (string.IsNullOrEmpty(style)) style = string.Empty;

      var myContainer = new DumpContainer {Content = content, Style = style};

      myContainer.Dump(title,toDataGrid);

  }

The custom method defined in the previous sample is called MyDump. The method takes three optional parameters to store the title that will be displayed at the top of the results, to set the style (in CSS) that will be used to display the results, and a Boolean value indicating whether the results will be presented in a data grid or in Rich Text Format. When no title or styles are passed, the code ensures the DumpContainer’s Dump method receives empty strings for those parameters. In the case of the toDataGrid parameter, the default value passed to the method is false when no value is specified for the parameter.

Using the custom Dump method in a query

The following query uses the custom Dump method to change the background and the foreground colors on the fly for the results displayed.

Code Listing 40: Testing Custom Dump

var states = States.Take(20);

states.MyDump("Custom Dump","background-color: #F4D7A3; color: #DE8900;",false);

As shown in this code, the string passed to the style parameter contains CSS code that defines colors for the background and for the text that will be displayed for the query results. These will be used instead of the colors defined in the default style sheet for text results (which can be customized in the Results tab of the Preferences dialog box).

Now, when the query is executed, the following output is displayed.

The Custom Dump Method in Action

Figure 31: The Custom Dump Method in Action

Tip: The font-family CSS style property can be used as a part of the style string to change the font used for displaying results.

Chapter summary

LINQPad provides elements to extend its power by letting you write your own classes or methods to improve several features included in the product. Those elements include custom extensions and custom visualizers.

LINQPad allows you to write custom methods and make them available for all queries. This is done by using the My Extensions node tab, which is located beside the My Queries tree view. This node creates a query with a static class definition called MyExtensions, where all custom methods should be defined. The query should be compiled in order to make those methods available for all queries. This compilation will create a custom assembly called MyExtensions.FW40.dll inside the LINQPad Plugins folder.

It’s possible to package custom extension methods as a plugin for LINQPad. This is done by creating a class library project in Visual Studio and adding a reference to LINQPad.exe into that project. Once the project is built, you should copy the assembly DLL, and any dependencies into LINQPad’s plugins folder (usually Documents\LINQPad Plugins).

LINQPad can display the output generated by a query in two ways. The first and most common way is by calling the Dump extension method, which creates an instance of the DumpContainer object for displaying the results. The second way is by using the DisplayControl and DisplayWpfElement methods of the PanelManager static class, which creates an instance of an OutputPanel object and returns it to the calling process.

Both the PanelManager static class and the DumpContainer object can be accessed from LINQPad queries, and can also be used in custom extension methods declared in MyExtensions or packed into a custom assembly.

The PanelManager static class allows you to display Windows Forms controls or WPF elements as part of the query results, and the events associated to these controls can be trapped in queries, allowing the user to interact with them.

The DumpContainer object allows you to create a customized version of the Dump extension method. This customized version can be used, for example, to dynamically change the style used to display results.


Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.