This article is compatible with the latest version of Silverlight.
In the previous part of this series we have investigated on how the Silverlight Object Model works behind the scenes. We have seen that it replicates more or less the same structure of the Server Object Model. We have also understood that we must call both Load(…) and ExecuteQueryAsync(…) methods before accessing the value properties of the client objects. Furthermore, sniffing the web traffic we have also seen that these actions are simply transformed into a WebRequest which is launched using the default credentials. We have learned that we can make even synchronous calls using ExecuteQuery(…) instead of ExecuteQueryasync(…) in a secondary thread. Eventually, I anticipated my idea to build a Silverlight application capable of surfing the object hierarchy of a Sharepoint site. Now it’s time to see it in action. Here you can watch a video showing how you can use it. Here you can download the source code.
A simple “Sharepoint navigator” application
If you look around you may find some great open source projects that allow you to get tons of information from a Sharepoint installation. For instance, there is the Sharepoint Manager which is not only an impressive Sharepoint browser but also a very useful editor. There is also the Sharepoint 2010 explorer, a remarkable Winform app using the Managed Client Object Model. So I wondered: why not creating something similar in Silverlight? This is a good opportunity to see how far we can go with Silverlight and at the same time a way to study the object model of Sharepoint.
In the image below you can see a screenshot of the application:
I want to say in advance that this application has to be considered as an experiment and it is not in any way comparable with the above mentioned projects in terms of functionalities. However, I think it is a good starting point if someone wishes to improve it in order to create a nice tool. On the left side there is a treeview containing a hierarchy of Sharepoint objects starting from the root object i.e. the “Site” object. On the main central area, on top, there is the type of the object selected in the treeview, and below there is the DataGrid containing all the readable properties of the selected object while in the window in blue there is the query used to obtain the property types and values of the object selected.
How to use the application
The Visual Studio solution of the application (downloadable here) contains 2 projects; the first is the Silverlight project itself and the other one is the project of the Sharepoint Visual Web Part which hosts the Silverlight application. All you have to do is build and deploy the web part (called “SP_SL_NavigatorVWP”) to your Sharepoint site and then insert it in a page. The mechanism is the same as I described in one of my previous articles. Just look here at the paragraph “Deploy the Silverlight application using a Visual WebPart” for all the details. Once the application has been started it shows just one item on the treeview, i.e. the “Site” object as in the picture below:
From here you can explore the objects hierarchy in two ways (or using a combination of the two ways):
1) Using a pre-packaged query
2) Using a hand-packed query
One right click on the “Site” item shows the following Context Menu with the two options:
Remember what we said in the introduction: before accessing the value properties of the client objects you have to load a query and launch the request to Sharepoint. Programmatically speaking it means that you should provide a lambda expression to the Load method of the Client Context object describing the data you want to know . To explain it better let me recall an example from one of my previous articles :
myClContext = new ClientContext("http://My_Server_name");
myClContext.Load(myClContext.Web.Lists);
myClContext.ExecuteQueryAsync(OnConnectSucceeded, OnConnectFailed);
In the code above we want to know more about the Web object and its Lists. Looking at the definition of the Load(…) method:
public void Load<T>(
T clientObject,
params Expression<Func<T, Object>>[] retrievals
)
where T : ClientObject
We could rewrite the example above using a lambda expression in the following way:
myClContext = new ClientContext("http://My_Server_name");
myClContext.Load(myClContext.Web, item => item.Lists);
myClContext.ExecuteQueryAsync(OnConnectSucceeded, OnConnectFailed);
With the “pre-packaged query” option a lambda expression is automatically created using all the available properties of the client object selected; we will come back on this later. With the “hand-packed query” we have to build the expression by hand.
The “hand packed query” option
Once you have clicked on the “hand packed query” option you will see the basic editor below:
In the black TextBox you can insert a lambda expression asking for information related to the Client object selected in the treeview. Since it is a very rudimental tool do not blame it on me if your gorgeous and complicated query will not be recognized J. See it as an exercise to understand the object model of Sharepoint. For instance, in the image above we see that the “Site” object has some properties; one is called “Features”, another one “RootWeb” and so on. What if we want to know more about these properties? Just write the query as I did and click OK.
The “pre-packaged query” option
With this option the query is pre-packaged for you. It is done by collecting all the properties available to the client object selected, creating a lambda expression for each of them, storing all these expressions in an array and passing the array to the Load(…) method. The application provides a “query details” inspector which allows you to see the query just executed as in the blue box of the picture below:
In detail, the underlying mechanism responsible for packaging the query performs the following actions:
1) it uses Reflection to create a MethodInfo instance representing the Load(…) method of the client object as in the code snippet below:
public static MethodInfo GetLoadMethod(ClientContext cliContext, ClientObject cliObject)
{
MethodInfo miLoad = cliContext.GetType().GetMethod("Load", BindingFlags.Instance | BindingFlags.Public);
miLoad = miLoad.MakeGenericMethod(cliObject.GetType());
return miLoad;
}
2) it gets all the readable properties of the client object as in the code snippet below:
var clientProps = locCLientObject.GetType(
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Select(p => p)
.Where(p => propsToSkip.Contains(p.Name) == false);
3) it builds a lambda expression for each property as in the code snippet below:
public static Linq_Expression GetLambdaForLoadMethod(ClientObject cliObject, PropertyInfo prop)
{
Type cliType = cliObject.GetType();
Linq_Expression exp = null;
try
{
// we want to build an expression like: Expression<Func<T, object>>
// 1. builds the delegate type
Type delegType = typeof(Func<,>).MakeGenericType(cliType, typeof(object));
// 2. builds the parameters of the expression
ParameterExpression parmsExp = Linq_Expression.Parameter(cliType, "i");
// 3. builds the body expression
MemberExpression bodyExp = Linq_Expression.Property(parmsExp, prop);
// 4. converts the body expression to a type compatible with the type arguments of the delegate type
UnaryExpression bodyExpConverted = Linq_Expression.Convert(bodyExp, typeof(object));
// creates lambda expression
exp = Linq_Expression.Lambda(delegType, bodyExpConverted, parmsExp);
}
catch { }
return exp;
}
4) it creates the array of lambda expressions and uses the MethodInfo instance to invoke the Load(…) method and pass the array:
expressionsArray = Array.CreateInstance(exprType, clientProps.Count());
int propCounter = 0;
foreach (PropertyInfo prop in clientProps)
{
Linq_Expression lamba_exp = SL_SPUtils.GetLambdaForLoadMethod(locCLientObject, prop);
object converted_lamba_exp = Convert.ChangeType(lamba_exp, exprType, CultureInfo.CurrentCulture);
expressionsArray.SetValue(converted_lamba_exp, propCounter);
expressionsList += lamba_exp.ToString().Replace("Convert", "") + "\r\n";
propCounter += 1;
}
// invoke Load with parameters
MethodInfo methodLoad = SL_SPUtils.GetLoadMethod(locClientContext, locCLientObject);
try
{
methodLoad.Invoke(locClientContext, new object[] { locCLientObject, expressionsArray });
}
catch (){}
The approach is more or less the same used in the source code of the already mentioned Sharepoint 2010 explorer which inspired me in fact.
Navigate and learn Sharepoint using the application
The title of this section summarizes the final objective of this article. As we have already seen, at a first glance the application shows only a root client object called “Site” and its properties:
No query has been made so far. This is just what we have on hand after the initialization of the ClientContext object:
ClientContext myClientContext = new ClientContext("http://my_site”);
Site mySite = myClientContext.Site;
This is not much but it is enough to say that the “Site” does not represent just a “Web Site” as we could imagine, but a site collection - basically a root web site with its sub-sites. In fact, if you look at the properties Grid you will find a property called “RootWeb” which contains information about the main web site and the underlying sites. You can see also other interesting properties which in some case reflect functionalities of Sharepoint. For instance, the Property “Features” is a “ClientObjectCollection”, namely a collection of client objects representing a “Feature”. What is a “Feature” in the Sharepoint world? Well, the concept behind that is not exactly what you may expect: we could define it as a single functionality packaged in order to be activated, deactivated, updated or deleted. Its scope can be at farm level or a web application level or even a site level. The various editions of Sharepoint 2010 come with a different number of built-in features; for instance, the “Team Collaboration Lists” and the “Group world Lists” area feature included in all the version of Sharepoint. They provide team collaboration capabilities like document libraries, calendars and so on. A feature is often used to customize a site; even the VS2010 project of the application of this article contains a feature:
And you can activate or deactivate from the “site settings/Features” of your site:
Let me take a step forward now; I want to know more about the “RootWeb” property; using the “hand-packed” query option you can write something like this:
And the result is:
As you can see, the RootWeb is quite a big object which represents the main site (look at the site title highlighted in red); it contains, among others, the “Webs” ClientObjectCollection. If we now create another query on this object, for instance writing the following in the editor: “item => item.Webs” we will be able to see all the subsites of our site as in the image below:
As you may notice I have just one subsite called “Documents Site” in my main site.
From here you could continue to explore the client object model either using the “hand-packed query” option or the “pre-packaged query” option to your choice, bearing in mind that the first option lets you focus on investigating one or few properties at a time, while the second option asks the server for all the properties, and it is obviously heavier.
Summary
In this article we have seen that using Silverlight we can build a small tool capable of navigating in the hierarchy of the client object model that Sharepoint 2010 puts at disposal of the Silverlight developers. We have also seen that it can be used as a kind of compute based training to learn the concepts of the Sharepoint world.
About the Author
Walter Ferrari is an environmental engineer and cultivates his passion for software development for a long time. He is currently a consultant working for his company, Abertech. He develops applications based on Microsoft technologies since 1995 and works primarily with .NET since 2003. He is currently focused on Silverlight and Sharepoint and acts as representative of CompletIT/SilverlightShow.
Walter is used to wearing a helmet while writing code..just to avoid serious damages when slamming his head against the monitor :) He blogs about his passion at http://www.snello.it/eng