 |
|
If you have a question or suggestion, please contact me through Windows Live Messenger. My status:
 . If I am not online, please send me an e-mail. |
|
|
|
|
 |
|
|
|
|
|
|
|
|
3/7/2010I just finished rebuilding my SharePoint 2010 development environment and started to build a new version of the Content By Type web part for SharePoint 2010. I decided to do a “Hello World” first to make sure everything works as expected. Some details on my dev environment: - Machine1: Windows 2008 R2
- Machine2: Windows 2008 R2
- SQL Server 2008 Express
- SharePoint 2010
- Visual Studio 2010
When I am developing, I am logged on to Machine2 as a domain user, who is a member of the local Administrators group. The application pool used by SharePoint run using different service accounts. I switched off User Account Control, because it is a virtual machine used for development purposes, and it saves me a lot of messages and restarting Visual Studio. To find the settings, type ‘UAC’ in the start menu search box. In Visual Studio I created a new empty SharePoint project. I pointed it to my development test site in SharePoint and added a web part. Wrote the code to display the “Hello World” message and hit F5. The solution compiled fine, but deployment failed: Error occurred in deployment step 'Recycle IIS Application Pool': The local SharePoint server is not available. Check that the server is running and connected to the SharePoint farm. This error occured because my domain user does not have the correct permissions in these databases: - SharePoint_Config
- SharePoint_AdminContent_[guid]
I added my domain to the user to the database (db_owner) and that fixed the problem. Next error message I got is this: Error occurred in deployment step 'Recycle IIS Application Pool': Cannot connect to the SharePoint site: http://intranet/. Make sure that this is a valid URL and the SharePoint site is running on the local computer. If you moved this project to a new computer or if the URL of the SharePoint site has changed since you created the project, update the Site URL property of the project. Same issue here, the domain user does not have the correct permissions for the content database of the web application. I added the domain user to the db_owner role of this database and things started to work: If you start running into issues like this, the SharePoint ULS logs turn out to be a very good starting point to find out what is wrong. Hope this saves some of you some time if you are using a similar setup. It will probably same myself some time next time I setup a new environment. 3/2/2010A new release (version 1.1) of SharePoint Objects is available on CodePlex. In this new release Features and SharePoint groups are added. If you are interested in a general overview of SharePoint Objects, read this blog post, or watch this screencast. The same principles showed here now also apply to features and group. Feature definitions After configuring a number of feature definitions in SharePoint Objects, all sites, site collections, web applications and the farm are added to the database if they are using the selected feature definition. The screenshot below shows all sites that have the “CustomList” feature activated. You can filter the list of feature definitions that are installed by scope. In my Farm there are 4 site collections based on the Collaboration Portal template. They all 4 have a Document Center site, which has the CustomList feature activated. Therefore the list shows 4 document centers. The link directly takes you to the Manage Features page for that site (depending of the scope of the selected feature definition). The link in the Site column leads you to the site itself. SharePoint Objects for Feature Definitions is available from Central Administration. In the SharePoint Objects section, click the link Feature definitions: SharePoint Groups SharePoint Objects now can also give you insight into the usage of your SharePoint groups, even across site collections! All securable objects in SharePoint (list item, folder, list/document library, site) are scanned. If the securable object has unique permissions, and the selected group has a role assigned, the securable object is added to the SharePoint Objects index and associated with that group. This allows you to quickly find all SharePoint artifacts that have unique permissions for the group “Home Members” for example. If you are using this group name across multiple site collections and web applications, and you index by Internal name, SharePoint Objects will show these artifacts across the boundaries of the current site collection. The screenshot below shows all SharePoint artifacts that have a role assignment for the group Home Members. In the list you see a number of different object types. A number of sites (in multiple site collections) have an assignment. You will also find a number of lists/document libraries, folders and even list items. The List column shows a direct link to the default view of the list or document library in case of a list, folder or item. If you navigate to the site settings page, the SharePoint Objects section now contains a new menu option, called SharePoint Groups: Summary The new release is available on CodePlex. If you are upgrading from the first version, let me know and I will send you the upgrade instructions. 2/28/2010For the new release of SharePoint Objects I am working on indexing the references to SharePoint groups. By using SharePoint Objects you can easily see which securable objects in SharePoint (site, list, folder, item) have a role assignment for a specific SharePoint group. I want the url for the reference to point directly to the Edit Permissions page in SharePoint for every type of object: The Edit Permissions page is the editprms.aspx page in the layouts folder. This page takes a number of parameters to load the permission settings for a specific object. The sections below describe the format of the url for each securable object type: SharePoint site (SPWeb) The Edit Permissions url for a SharePoint site looks like this: /_layouts/editprms.aspx?obj=[OBJECTID]%2C[OBJECTTYPE]&sel=[GROUPID] where: [OBJECTID] is the encoded full url of the site. [OBJETTYPE] is ‘WEB’. [GROUPID] is the ID of the group The code to construct the url: refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2CWEB&sel={2}", web.Url, HttpUtility.UrlEncode(web.Url), group.ID);
Example:
http://tstmss/_layouts/editprms.aspx?obj=http%3a%2f%2ftstmss%2CWEB&sel=5
SharePoint List (SPList)
The Edit Permissions link for a list object:
/_layouts/editprms.aspx?obj=[OBJECTID]%2C[OBJECTTYPE]&sel=[GROUPID] where: [OBJECTID] is the encoded ID url of the list. [OBJETTYPE] is ‘LIST’ for a list and ‘DOCLIB’ for a document library. [GROUPID] is the ID of the group
The code to construct the url:
if (list is SPDocumentLibrary) { refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2CDOCLIB&sel={2}", web.Url, HttpUtility.UrlEncode(list.ID.ToString("B")), group.ID.ToString());} else { refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2CLIST&sel={2}", web.Url, HttpUtility.UrlEncode(list.ID.ToString("B")), group.ID.ToString());}
Example:
http://tstmss/_layouts/editprms.aspx?obj=%7ba17a2cb1-a103-41fe-af46-5785c4838e6f%7d%2CDOCLIB&sel=5
SharePoint folder (SPFolder)
The Edit Permissions link for a folder in a document library of a SharePoint list:
/_layouts/editprms.aspx?obj=[OBJECTID]%2C[OBJECTTYPE]%2C[ITEMID]&sel=[GROUPID] where: [OBJECTID] is the encoded ID url of the list that contains the folder. [OBJETTYPE] is ‘FOLDER’ for a folder in a list and ‘DOCLIBFOLDER’ for folder in a document library. [ITEMID] is the ID of the listitem for the folder. [GROUPID] is the ID of the group
The code to construct the url:
if (folder.Item.ParentList is SPDocumentLibrary) { refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2C{2}%2CDOCLIBFOLDER&sel={3}", web.Url, HttpUtility.UrlEncode(folder.ParentListId.ToString("B")), folder.Item.ID, group.ID);} else { refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2C{2}%2CFOLDER&sel={3}", web.Url, HttpUtility.UrlEncode(folder.ParentListId.ToString("B")), folder.Item.ID, group.ID);}
Example:
http://tstmss/_layouts/editprms.aspx?obj=%7ba17a2cb1-a103-41fe-af46-5785c4838e6f%7d%2C4%2CDOCLIBFOLDER&sel=5
SharePoint list item (SPListItem)
The Edit Permissions link for an item in a SharePoint list or document library:
/_layouts/editprms.aspx?obj=[OBJECTID]%2C[OBJECTTYPE]%2C[ITEMID]&sel=[GROUPID] where: [OBJECTID] is the encoded ID url of the list that contains the item or document. [OBJETTYPE] is ‘LISTITEM’ for an item in a list and ‘DOCUMENT’ for an item in a document library. [ITEMID] is the ID of the listitem or document. [GROUPID] is the ID of the group
The code to construct the url:
if (item.ParentList is SPDocumentLibrary) { refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2C{2}%2CDOCUMENT&sel={3}", web.Url, HttpUtility.UrlEncode(item.ParentList.ID.ToString("B")), item.ID, group.ID);} else { refUrl = String.Format("{0}/_layouts/editprms.aspx?obj={1}%2C{2}%2CLISTITEM&sel={3}", web.Url, HttpUtility.UrlEncode(item.ParentList.ID.ToString("B")), item.ID, group.ID);}
Example:
http://tstmss/_layouts/editprms.aspx?obj=%7bd8a32282-cc74-4635-89a2-06270f50f6b8%7d%2C3%2CLISTITEM&sel=5
Summary
This post describes what parameters you need to pass to the Edit Permissions page, for the different types of objects that you can assign permissions to.
If you are interested in SharePoint Objects in general, watch this screencast or visit my CodePlex site. 2/23/2010In this screencast I will give you an overview of my new CodePlex solution, SharePoint Objects. Click the link below to watch the video (I am working on how to embed it in this post). Introduction SharePoint Objects from Ton Stegeman on Vimeo. Links to other relevant SharePoint Objects content: 1/30/2010In one of the projects we are working on, we created a number of Reporting Services and integrated them in SharePoint 2007. This report displays SharePoint data in a printable, exportable report on a SharePoint page. We started of with using a report deployed to our Reporting Services server. In this proof of concept stage, we looked at using the Enesys RS Data Extension. A pretty clever datasource for SharePoint. It did not exactly meet our requirements, so we had to look at another solution. One of my colleagues pointed me to the ReportViewer controls in Visual Studio. This includes an ASP.NET control that is able to display reports in a web page. In this article I will describe how I created a web part that displays an aggregation of SharePoint tasks in a report. For the end user the report looks like this: The user has a dropdown box in the web part that left him/her select a task status. After clicking the button, the reporting web part queries the site collection for all Task list items with that status and puts them in the report. A big pro that we got by using this control is that we now bypass the Report Server. The report itself is deployed as a resource in the assembly. Therefore we do not need to deploy the report and its permissions. And we bypass the double hop. Nice. By using our Reporting Services server, we would go from SharePoint to the Report Server to get the report. The report server would need to connect back to the SharePoint server to get the data. By using the ReportViewer, we bypass this, because everything happens on the server. As you can see from the screenshot above, this article is not about my skills to build a nice report. I just want to show you how to display a report in SharePoint that is using SharePoint data. At the end of this post you will find a link to a ZIP file containing all sources used in this article. Step 1 – Setup In this article I am using Visual Studio 2008, the .NET Framework 3.5 SP1 (this also needs to be available on the SharePoint servers). After creating a new Visual Studio project, (Class library) I targeted the .NET framework to 3.5. Next thing to do is add a reference to the reporting assembly. This control is available in the dll Microsoft.ReportViewer.WebForms.dll, that can be found in folder ‘C:\Program Files\Microsoft Visual Studio 9.0\ReportViewer’. Your solution installer also needs to make the assembly available on the SharePoint server(s). The the reportview is not yet available on the SharePoint servers, you need to deploy these assemblies: - Microsoft.ReportViewer.WebForms.dll
- Microsoft.ReportViewer.Common.dll
- Microsoft.ReportViewer.ProcessingObjectModel.dll
In the web.config of your SharePoint web application(s) you will need to make some changes. First thing to do is to register the HttpHandler. Add this snippet in the HttpHandlers section: 1: <add verb="*" 2: path="Reserved.ReportViewerWebControl.axd" 3: type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
In the appSettings section, you need to remove this element:
1: <add key="ReportViewerMessages" 2: value="Microsoft.SharePoint.Portal.Analytics.UI.ReportViewerMessages, Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
If you do not remove the element, your reporting web part will display this error message at runtime: “The type Microsoft.SharePoint.Portal.Analytics.UI.ReportViewerMessages, Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c does not implement IReportViewerMessages or could not be found”. Now your Visual Studio project and your SharePoint are ready to get started.
Step 2 – Create the business objects
The report will use an ObjectDataSource as the datasource. In this step we will create the businessobjects to be used as the datasource. Visual Studio can help you pretty good if you want to connect to SQL Server to get the data for your report. Unfortunately, Visual Studio does not know about SharePoint, and therefore you will have to do the coding around the datasource yourself. First object to create is the Task object. This object is very simple:
1: public class Task 2: { 3: public Task() 4: { 5: } 6: 7: public String Title { get; set; } 8: public String Status { get; set; } 9: public String Priority { get; set; } 10: public String AssignedTo { get; set; } 11: public DateTime DueDate { get; set; } 12: 13: public int DueDays 14: { 15: get 16: { 17: return DueDate.Subtract(DateTime.Now).Days; 18: } 19: } 20: }
The TaskList object is an object that holds a number of Task objects and is the object that will be used as ObjectDataSource. Therefore this class is decorated with the DataObjectAttribute attribute (from the System.ComponentModel namespace). This class has a parameterless constructor and a public method that returns a List populated with Task items.
1: [DataObjectAttribute(true)] 2: public class TaskList 3: { 4: public TaskList() 5: { 6: } 7: 8: [DataObjectMethod(DataObjectMethodType.Select)] 9: public List<Task> LoadByStatus(String status) 10: { 11: List<Task> taskList = new List<Task>(); 12: SPSiteDataQuery taskQuery = new SPSiteDataQuery(); 13: taskQuery.Lists = "<Lists ServerTemplate=\"107\" />"; 14: taskQuery.RowLimit = 10000; 15: String query = String.Format(@"<Where><Eq><FieldRef Name='Status' /><Value Type='Text'>{0}</Value></Eq></Where>", status); 16: taskQuery.Query = query; 17: taskQuery.ViewFields = "<FieldRef Name=\"Title\" /><FieldRef Name=\"AssignedTo\" /><FieldRef Name=\"Priority\" /><FieldRef Name=\"DueDate\" Type=\"DateTime\"/>"; 18: taskQuery.Webs = "<Webs Scope=\"SiteCollection\" />"; ; 19: DataTable projectsTable = SPContext.Current.Web.GetSiteData(taskQuery); 20: foreach (DataRow row in projectsTable.Rows) 21: { 22: Task task = new Task(); 23: task.Title = GetFieldValue(row, "Title"); 24: task.Priority = GetFieldValue(row, "Priority"); 25: task.AssignedTo = GetFieldValueUser(row, "AssignedTo"); 26: task.DueDate = GetFieldValueDateTime(row, "DueDate"); 27: taskList.Add(task); 28: } 29: return taskList; 30: } 31: }
The function that is called by the ObjectDataSource is LoadByStatus. This takes a status string as a parameter and returns a List of Task items. The class also contains a number of helper methods to get the data from the DataTable. They are irrelevant for this blog post, but they can be found in the source code that you will find in the ZIP file at the bottom of this article.
Step 3 – Create the report
Now that we have our business objects ready, we can create the report. In your Visual Studio project, click “Add new item” and select the “Report” from the Reporting page:
Select the rdlc file you just added in the Solution Explorer and set the Build Action to Embedded Resource.
In the Datasources windows, click the Add New Data Source button:
In the next dialog, select Object as the Data Source Type:

The next step in the wizard asks you to which object you with to bind. Select the Task object, because that is the object we want to use in our report:
In the Report menu of Visual Studio, select Data Sources.
In the dialog select the datasource object you just added, and click Add to report.

Add a table to the report and drag one of the fields to the detail row:
From this point on, it is business as usual (if you are used to building reports in Reporting Services).
Step 4 – Create the reporting web part
Last thing to do it to create a new web part that will render the report. The snippet below shows the part of the CreateChildControls method of the task reporting web part that created the objects we need. Full code (that also creates the dropdownlist and the button) can be found in the ZIP file at the bottom of this post.
1: _reportViewer = new ReportViewer(); 2: _reportViewer.Width = new Unit(100, UnitType.Percentage); 3: _reportViewer.Height = new Unit(100, UnitType.Percentage); 4: _reportViewer.Visible = false; 5: _reportViewer.LocalReport.ReportEmbeddedResource = "TST.SharePoint2007.Reporting.TaskReport.rdlc"; 6: Controls.Add(_reportViewer); 7: 8: _datasourceTaken = new ObjectDataSource(); 9: _datasourceTaken.ID = "datasourceTasks"; 10: _datasourceTaken.SelectMethod = "LoadByStatus"; 11: _datasourceTaken.SelectParameters.Add(new Parameter("status", System.Data.DbType.String)); 12: _datasourceTaken.TypeName = "TST.SharePoint2007.Reporting.TaskList, TST.SharePoint2007.Reporting, Version=1.0.0.0, Culture=neutral, PublicKeyToken=503edd7b21a430b3"; 13: _datasourceTaken.Selecting += new ObjectDataSourceSelectingEventHandler(datasourceTasks_Selecting); 14: Controls.Add(_datasourceTaken);
After creating the ReportView control, the reference to the report file needs to be set. Our report is an embedded resource in the assembly. Therefore we seet the ReportEmbeddedResource property of the LocalReport (in line 5). The full name to load the report is the name of the assembly (TST.SharePoint2007.Reporting) followed by the name of the report (TaskReport.rdlc).
After creating the viewer control, the ObjectDataSource is created. The TypeName property in line 12 is the reference to the business object we created in step 2. The SelectMethod and SelectParameters need to match a public function decorated with the DataObjectMethod attribute in that classe. In my case that is the function LoadByStatus, which has a parameter called status of type String (see step 2). The snippet below shows the implementation of the Selecting event.
1: private void datasourceTasks_Selecting(object sender, ObjectDataSourceSelectingEventArgs e) 2: { 3: e.InputParameters["status"] = _getStatus.SelectedValue; 4: }
In this event we get the value of the status dropdownlist (see the first screenshot) and pass the value to the inputparameter of the datasource. Last thing to do it to implement the event handler for the button. When a user clicks the “Generate Report” button, this method is called:
1: private void search_Click(object sender, EventArgs e) 2: { 3: ReportDataSource dataSource = new ReportDataSource("TST_SharePoint2007_Reporting_Task", _datasourceTaken); 4: _reportViewer.LocalReport.DataSources.Clear(); 5: _reportViewer.LocalReport.DataSources.Add(dataSource); 6: _reportViewer.Visible = true; 7: _reportViewer.LocalReport.Refresh(); 8: } 9:
In this method, a new ReportDataSource object is instantiated and added to the report. Please note that the first parameter of the constructor, the name parameter, needs to match the name of the datasource in the report. This is the name of the data source you selected in step 3 in the Report Data Sources dialog:

The second parameter of the ReportDataSource constructor is a reference to the ObjectDataSource we created in CreateChildControls.
After building and deploying your web part, you should now be able to run the report.
Conclusion
The .NET ReportViewer control is a powerful control to use if you need to do reporting on SharePoint data in SharePoint. It saves you from managing a report server. You don’t need to deploy the report, because it it part of the assembly and you do not have to worry about the double hop issue. Another option would be to run Reporting Services in integrated mode with SharePoint. In our case, that was not an option. We had very specific requirements for the data retrieval process and therefore we moved in this direction.
Full source code for this sample can be found in this ZIP file. 1/13/2010
Introduction
[Update 28-02-2010]: Watch the introduction screencast at vimeo.
This blog post is the introduction of my new CodePlex project, SharePoint Objects. SharePoint Objects is built to give you insight in usage of SharePoint 2007 artifacts in your farm. You probably have seen the message “The content type is in use” a number of times when you tried to delete a content type. Then the big search for all other content types, lists and items that are using the content type began and probably ended in frustration. Or you are going to make a change to one of the site columns and you want to understand the impact of this change. The only way to do this in SharePoint is by manually going through all your sites and lists.
This is where SharePoint Objects is going to help you. It creates a dedicated database in your farm. You can instruct SharePoint Objects to index specific objects and all references to that object into the database. You can use the SharePoint Objects pages to display the information in this database. This will help you to quickly find all places in SharePoint 2007 where your artifact is used. The solution comes with a timer job that automatically keeps this information up to date, based on the schedule that you specify. The screenshot below shows the overview of the usage of the out of the box “Article Date” field.
It shows three different types of objects that are using the site column in the site collection. The list shows the name of the referencing object, including a link to the details page of that referencing object. It also shows the site and the list that contain the object. You can filter the list by using the search options just above the list, or by using column filtering just as in ordinary SharePoint lists.
The release that I am now introducing is the first beta release. This version indexes site columns and content types. For content types these types of object references are indexed:
- Content type associated to a list: SharePoint lists that are using the selected content type.
- Content type of a list item: List items of the selected content type.
- Content type is parent of a content type: Other content types that are using the selected content type as parent.
For site columns these types of usage of the site column are indexed:
- Site column associated to a list: SharePoint lists that have the selected field associated.
- Site column used by a list item: list items that have a value for the selected field.
- Site column associated to a content type: Content types that have an association with the Article Date field.
At the bottom of this post, you will find my ideas about the future of SharePoint Objects. After installation of SharePoint Objects (you can download installer and the installation guide here) you need to specify which objects will be indexed by SharePoint Objects.
Specifying objects to index
After installing the solution, you need to tell SharePoint Objects which objects to index. Before you do this, make sure you completed all configuration steps that are described in the Installation Guide. In these steps you will setup the database and configure menu options and the timer job. The site settings menu of all SharePoint sites now have a new section called SharePoint Objects. The page that you select will show you all objects that are defined in that site. Please be aware that it only shows the objects that are defined in the current site. It will not show all available site columns or content types. Therefore the list of objects will be long in the root site of your site collections. In the sample screenshots below, I am using an example of a site column, but the same principles apply to content types.
The left side of the screen shows information about the current site. It shows when the site was last indexed. Just below that you will find the section that shows all artifacts, defined in this site, that are currently monitored by SharePoint objects. In my case, this list is empty, because we are going to configure the first object. Use the group dropdown or the search box to find your site column and click the radiobutton.
The top right of your screen will now show the options for the site column. First you need to select the Index scope. You can choose from one of these 4 scopes:
- Site – Just objects in the current site are indexed.
- Site Collection – The indexer will iterate through all sites in the current site collection to find objects that use the site column.
- Web Application – All site collections in the current web application will be monitored for references to the Article Date site column.
- Farm – All artifacts in all web applications in your farm are checked for references to the selected site column.
The second option is to select Index by property. The indexer uses this value to decide if a SharePoint artifact has a reference to the selected site column. The dropdown has these options:
- Id – Checks the reference of for example a site column association to a list based on the ID (the GUID) of the site column. If the GUID of the Article Date field matches the guid of any of the list fields, the list is included in the index. This is a useful option for the out of the box site columns and the site columns that you have deployed through features, in which you specified the ID for a field.
- Display name – If you manually created the same site columns across multiple site collections and web applications, the ID for these site columns will be different. You can then instruct the indexer to decide whether or not it is a reference by looking at the display name of the field. In my example, a
- Internal name – Same as display name, but now the internal name of the field is used. If you promote your users to categorize their documents using a Category field, you can use SharePoint Objects to get an idea of user groups in your organization that are using this.
If you want to index the selected site column, you check the box Include in index. If you want SharePoint Objects to include references to list items in the index, you tick the Index list items box. If this is not selected, list items in the lists and libraries are not checked for references.
After setting the options, click the Save button. You can now wait for the timer job to kick in and index the objects you configured, or you can click the “Index now” link in the Site information section.
Finding references to your object
After setting up a number of objects and running the timer job, you can use the SharePoint Objects pages to find the references. You use the same page as you used to setup your artifacts in the previous chapter. The Indexed Site columns section now shows the objects that are monitored by the solution.
Select one of the site colums. The right side of the screen now again shows the options you just set for this object. Below that you will find the list of objects that have a reference to the Article Date field. You can use the dropdown boxes and the textboxes in the Objects using this site column section to find the references that you are looking for.
Please notice the 2 scope dropdowns in the screenshot above. The display scope is set to Site. So the list currently only shows all artifacts that use Article Date in the current site. The references are indexed using the web application scope, so I can change the dropdown and get an overview of all objects using Article Date, even in other site collections. If I had set the Index scope option to Site Collection however, the indexer just looked in the current site collection and selecting Web Application as the display scope would not show me more references. Just because they are not indexed.
You can also filter the list by using the column headers:
Running SharePoint Objects in your farm(s)
There are 2 ways to run the SharePoint Indexer. The first option is to configure the timerjob and specify a schedule. Please be aware that indexing is a process that uses a lot of system resources and is something that you would not want to do during business hours. In your Central Administration, the Application Management page has 2 new menu options:
Select the option Configure timerjob for the index job of SharePoint Objects. If you do not see these options, the web application feature is not activated for your web application. Check the Installation Guide for instructions. Specify a schedule for the timerjob and click OK.
Please note: the indexer runs in the context of a web application. That means that if you want to index content from multiple web applications, you need to activate the feature and configure the timerjob for every web application.
The future
As I said in the introduction, this is the first beta release. I hope you are going to give it a go, but please give it a pretty good test in a lab environment, before throwing it on the production server directly. I am interested to hear feedback from you how installation and configuration was. You are also invited to give me suggestions on how to improve SharePoint Objects. Am I missing references for site columns and content types, does everything work as expected, are all links shown in the overview correct? Things like that. I am also working on new artifacts to include in a next release. The list can get pretty long, I am currently thinking about:
- SharePoint groups
- Web parts
- Feature Definitions
- Page Layouts and master pages
- List schema’s
- List views
If you have artifacts that you want to index in the next release, please let me know and I will prioritize.
Here is the link to CodePlex: http://sharepointobjects.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=38702
Enjoy SharePoint Objects and hope you like it! 12/30/2009This post is a wrap up of previous blog posts that I have written on creating a custom list schema. I find myself searching for these posts every time I need to do this, so I thought it would be handy to have it all in one place (at least for myself). In one of our current projects, we were creating custom list schemas like this. we tried to query the lists (using Content Query Web Part) based on the custom content type that is associated with these list. At first this did not work and this post also shows how we fixed that. At the end of this article you will find the link to the ZIP file containing all code. This article shows how to: - Create a custom site column from a feature
- Create a custom content type using that site column from a feature
- Create a custom list schema using that content type
- Rename the Title field to a custom display name
- Add a new view to the list, to show the new site column
- Create a new feature that creates a new instance of the new list template
- Populate the list with default items
- Show items from lists based on this template using the Content Query Web Part
My solution has 2 features: - site collection feature for registering site column, content type and list template.
- web feature for creating a list and populating it with data.
Step 1 – The Site Collection Feature The XML snippet below is the XML from my feature.xml file that ends up in folder <SharePoint Root>\TEMPLATE\FEATURES\TST.CustomListTemplate. Nothing special to mention here, except that you can register multiple site columns, content types and list templates by creating 1 feature. You don’t need to create a new feature for every list template. SharePoint allows you to do that, but you end up with having a lot of features. If you want to have a feature for each list template, I would recommend creating hidden features and creating 1 wrapper features with activation dependencies. 1: <?xml version="1.0" encoding="utf-8"?> 2: <Feature Id="23ddef43-9d01-4127-9903-beeb77c28c5d" 3: Title="TST Custom List Template demo" 4: Description="Custom list template with a custom content type associated (Ton Stegeman)" 5: Version="1.0.0.0" 6: Scope="Site" 7: Hidden="FALSE" 8: xmlns="http://schemas.microsoft.com/sharepoint/"> 9: <ElementManifests> 10: <ElementManifest Location="Fields.xml"/> 11: <ElementManifest Location="Contenttype.xml"/> 12: <ElementManifest Location="ListTemplates\Template.xml" /> 13: </ElementManifests> 14: </Feature>
The sample above activates three element manifests. These can be found in the next paragraphs.
Step 2 – The Site Column
In this XML snippet below, the new site column is created.
1: <?xml version="1.0" encoding="utf-8"?> 2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <Field 4: Type="Text" 5: DisplayName="Demo Text Field" 6: Required="TRUE" 7: MaxLength="255" 8: Group="TST Demo Fields" 9: ID="{ee69b92a-02cd-4a5d-b6bd-d1fec35c301f}" 10: StaticName="DemoTextField" 11: Name="DemoTextField"/> 12: </Elements>
Most important thing to notice here is that I removed the spaces that are used in the displayname of the field from the internalname attributes of the field. I would recommend doing this for all special characters, otherwise you end up with encoded internal field names using ‘_x0020’. My custom site column would have had internal name ‘TST_x0020_Demo_x0020_Field’ if I did not remove the spaces from the internal names.
Step 3 – The Content Type
Below you can find the contents of Contenttype.xml. In the <FieldRefs> element you will find all fields that are used by the content type. You see a reference to our custom site column (it has the same ID and Name). The first field used in this sample is the title field that is renamed to have another displayname.
1: <?xml version="1.0" encoding="utf-8"?> 2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <ContentType ID="0x01008c43d032ae654d6e8b965c45acc4af3a" 4: Name="Demo Contenttype" 5: Description="Content type with a custom text field and a renamed Title field." 6: Group="TST Demo Fields" 7: Version="0" 8: Sealed="FALSE" 9: ReadOnly="FALSE" 10: BaseType="0x01"> 11: <FieldRefs> 12: <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" 13: DisplayName="Demo Title Field" 14: Name="Title" 15: Required="TRUE" 16: ShowInNewForm="TRUE" 17: ShowInEditForm="TRUE"/> 18: <FieldRef ID="{ee69b92a-02cd-4a5d-b6bd-d1fec35c301f}" 19: Name="DemoTextField" 20: Required="TRUE" 21: ShowInNewForm="TRUE" 22: ShowInEditForm="TRUE"/> 23: </FieldRefs> 24: </ContentType> 25: </Elements>
There is something special about the ID of a custom content type. If you haven’t seen this before, you better read the something of the backgrounds, to understand this mechanism. Brett Maytom explains how it works. This MSDN article by Scot Hillier also explains it briefly.
Step 4 – The List Schema
Next thing to create is the list schema that is using our custom content type. I won’t publish the full content of the schema.xml file. You can find this in the attached ZIP file (see the end of this article). Important in the schema is that you will need to have the XML definition of the Field elements that are in your content type.
1: <ContentTypes> 2: <ContentTypeRef ID="0x01008c43d032ae654d6e8b965c45acc4af3a"> 3: <Folder TargetName="Item" /> 4: </ContentTypeRef> 5: </ContentTypes> 6: <Fields> 7: <Field 8: Name="LinkTitle" 9: ID="{82642ec8-ef9b-478f-acf9-31f7d45fbc31}" 10: DisplayName="Demo Title Field" 11: Sealed="TRUE" 12: SourceID="http://schemas.microsoft.com/sharepoint/v3" 13: StaticName="LinkTitle"> 14: </Field> 15: <Field 16: Name="LinkTitleNoMenu" 17: ID="{bc91a437-52e7-49e1-8c4e-4698904b2b6d}" 18: DisplayName="Demo Title Field" 19: Sealed="TRUE" 20: SourceID="http://schemas.microsoft.com/sharepoint/v3" 21: StaticName="LinkTitleNoMenu"> 22: </Field> 23: <Field 24: ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" 25: Type="Text" 26: Name="Title" 27: ShowInNewForm="TRUE" 28: DisplayName="Demo Title Field" 29: Sealed="TRUE" 30: SourceID="http://schemas.microsoft.com/sharepoint/v3" 31: StaticName="Title"> 32: </Field> 33: <Field 34: Type="Text" 35: DisplayName="Demo Text Field" 36: Required="TRUE" 37: MaxLength="255" 38: Group="TST Demo Fields" 39: ID="{ee69b92a-02cd-4a5d-b6bd-d1fec35c301f}" 40: StaticName="DemoTextField" 41: Name="DemoTextField"/> 42: </Fields>
The snippet above shows how to associate the custom content type we created in Step 3 with the list. The Fields elements contains all fields that we will use in our list (also our custom field that is part of the content type). If you don’t put the XML for your fields here, the content type will be associated with the list, but the fields will not be available in the list items. The snippet also shows how to rename the Title field to have a custom display name. It also renames the out of the box computed fields that use this Title field (like ‘Title (linked to item with edit menu)’).
Step 5 – The View
In the schema.xml file we also add the new view to our list. The snippet below just shows the <View> element.
1: <View 2: BaseViewID="2" 3: Type="HTML" 4: WebPartZoneID="Main" 5: DisplayName="TST Custom View" 6: DefaultView="FALSE" 7: SetupPath="pages\viewpage.aspx" 8: ImageUrl="/_layouts/images/generic.png" 9: Url="DemoView.aspx">
Important attributes of this element are BaseViewID, SetupPath and Url. BaseViewID must be a unique ID for all views in your schema.xml file. SetupPath is a reference to a page that is used as template for your view page. If you don’t want to use custom form, set it to ‘pages\viewpage.aspx’. The Url will be the name of the aspx of the View itself. If you don’t specify the SetupPath, you will have to make sure your list schema folder has an aspx for the view page.
Step 6 – The List Template
After creating the list schema, next thing to do is to define the list template. This is the 3rd XML file that is referenced from our feature.xml file in step 1. The contents of my example is shown in the snippet below.
1: <?xml version="1.0" encoding="utf-8"?> 2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <ListTemplate 4: Name="List" 5: Type="90010" 6: BaseType="0" 7: OnQuickLaunch="TRUE" 8: SecurityBits="11" 9: Sequence="410" 10: DisplayName="Demo list template with custom content type (Ton Stegeman)" 11: Description="Custom list with a custom content type associated, a renamed title field and an extra view." 12: Image="/_layouts/images/itgen.gif"/> 13: </Elements>
Make sure that the Name attribute matches the name of the folder that contains the schema.xml file. In my case, the feature folder has a subfolder called ‘List’ that holds the schema.xml file. Second thing to look at is to define a unique number for your list template and use that in the Type attribute.
Step 7 – The Web Feature
In the previous steps we have completed our site collection feature that registers the schema for our custom list. In this step, we will create a Web scoped feature that creates a new instance based on this list schema and adds some items to it.
1: <?xml version="1.0" encoding="utf-8"?> 2: <Feature Id="05fd17d4-5f70-40ab-a5e9-975ba15df371" 3: Title="TST Custom List Instance demo" 4: Description="Custom list instance based on a custom schema (Ton Stegeman)" 5: Version="1.0.0.0" 6: Scope="Web" 7: Hidden="FALSE" 8: xmlns="http://schemas.microsoft.com/sharepoint/"> 9: <ElementManifests> 10: <ElementManifest Location="Lists.xml" /> 11: </ElementManifests> 12: <ActivationDependencies> 13: <ActivationDependency FeatureId="23ddef43-9d01-4127-9903-beeb77c28c5d"/> 14: </ActivationDependencies> 15: </Feature>
The above snippet shows the XML file the feature. Make sure the Id for your feature is unique. Create a new GUID using http://www.newguid.com, use GuidGen in Visual Studio, or use the CodeRush/Refactor tools for SharePoint Developers by Andrew Connell.
The feature has a dependency on our site collection feature. SharePoint will first check if this feature is activated before activating the web feature.
Step 8 – The List
The feature in step 7 uses Lists.xml as element manifest. The contents of this file is shown in the snippet below. The FeatureId attribute is a reference to the feature that registered the list template. This is our site collection feature from Step 1, not the web feature from step 7!
1: <?xml version="1.0" encoding="utf-8" ?> 2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <ListInstance 4: FeatureId="23ddef43-9d01-4127-9903-beeb77c28c5d" 5: Title="Demo list" 6: Description="Demo list with custom schema and items." 7: Id="90010" 8: TemplateType="90010" 9: Url="Lists/Demolist"> 10: </ListInstance> 11: </Elements>
Make sure that the Id attribute matches the Type attribute of the ListTemplate element in Step 6. If you have spaces in the title of your list, it might be good to remove them from the url for your list. The Url attribute allows you to specify the url for your list. This will be the name of the rootfolder of the list.
Step 9 – The Data
In this step, we populate the list with some data once the list is created. Add the snippet below as a child element of the ListInstance element in the XML file of step 8.
1: <Data> 2: <Rows> 3: <Row> 4: <Field Name="ID">1</Field> 5: <Field Name="Title">Title of the first item (created in the feature)</Field> 6: <Field Name="DemoTextField">Demo text first item</Field> 7: </Row> 8: <Row> 9: <Field Name="ID">2</Field> 10: <Field Name="Title">Title of the first item (created in the feature)</Field> 11: <Field Name="DemoTextField">Demo text first item</Field> 12: </Row> 13: </Rows> 14: </Data>
For every Row, the ID field is set explicitly. It is not necessary to do this, but if you don’t do it and your feature gets re-activated, you end up with duplicated content in your list.
Step 9 – The Deployment
Deployment of our 2 features is done using a WSP package. In my Visual Studio solution, the folder TSTCustomListTemplate contains the site collection feature and all content. The folder TSTCustomListInstance contains all content for the web feature. The package is created using the DDF file below:
1: ; 2: .OPTION EXPLICIT ; Generate errors 3: .Set CabinetNameTemplate=TST.TestList2007.ListTemplate.wsp 4: .set DiskDirectoryTemplate=CDROM ; All cabinets go in a single directory 5: .Set CompressionType=MSZIP;** All files are compressed in cabinet files 6: .Set UniqueFiles="ON" 7: .Set Cabinet=on 8: .Set DiskDirectory1=Package 9: manifest.xml manifest.xml 10: 11: ; Include files for the Feature folder 12: ..\TSTCustomListTemplate\Feature.xml TST.CustomListTemplate\Feature.xml 13: ..\TSTCustomListTemplate\Contenttype.xml TST.CustomListTemplate\Contenttype.xml 14: ..\TSTCustomListTemplate\Fields.xml TST.CustomListTemplate\Fields.xml 15: ..\TSTCustomListTemplate\ListTemplates\Template.xml TST.CustomListTemplate\ListTemplates\Template.xml 16: ..\TSTCustomListTemplate\List\schema.xml Features\TST.CustomListTemplate\List\schema.xml 17: 18: ..\TSTCustomListInstance\Feature.xml TST.CustomListInstance\Feature.xml 19: ..\TSTCustomListInstance\Lists.xml TST.CustomListInstance\Lists.xml 20: ;
The manifest of the solution:
1: <?xml version="1.0" encoding="utf-8" ?> 2: <Solution SolutionId="56e1580d-7eb8-4428-adea-1a17c120389c" xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <TemplateFiles> 4: <TemplateFile Location="Features\TST.CustomListTemplate\List\schema.xml"/> 5: </TemplateFiles> 6: <FeatureManifests> 7: <FeatureManifest Location ="TST.CustomListTemplate\Feature.xml"/> 8: <FeatureManifest Location ="TST.CustomListInstance\Feature.xml"/> 9: </FeatureManifests> 10: </Solution> 11:
Deploying the solution is done using SharePoint Solution Installer from CodePlex. The snippet below shows the configuration for the installer, from setup.exe.config. The SolutionId needs to be the same value as the value of the SolutionId attribute in the manifest file.
1: <?xml version="1.0" encoding="utf-8" ?> 2: <configuration> 3: <appSettings> 4: <add key="BannerImage" value="Default"/> 5: <add key="LogoImage" value="None"/> 6: <add key="EULA" value=""/> 7: <add key="SolutionId" value="56e1580d-7eb8-4428-adea-1a17c120389c"/> 8: <add key="FarmFeatureId" value=""/> 9: <add key="SolutionFile" value="TST.TestList2007.ListTemplate.wsp"/> 10: <add key="SolutionTitle" value="TST Demo List Template"/> 11: <add key="SolutionVersion" value="1.0.0.0"/> 12: <add key="UpgradeDescription" value="Upgrades {SolutionTitle} on all frontend web servers in the SharePoint farm."/> 13: <add key="RequireDeploymentToCentralAdminWebApplication" value="false"/> 14: <add key="RequireDeploymentToAllContentWebApplications" value="false"/> 15: </appSettings> 16: </configuration>
Step 10 – The Test
After installing and deploying the solution, you can test the new list. First activate the site collection feature:
In the site(s) where you want to use your new list, activate the web feature:
If all is well, you now have a new list in your site called ‘Demo list’:
This list should have a custom view (TST Custom View), the Title field is renamed to ‘Demo Title Field’ and it has 2 items, as shown in the screenshot above.
Step 11 – Querying The Content
Last step in this article is to show how you can query for content in your new list(s) using the Content Query Web Part. Drag the web part onto a page and modify the properties. In the Query section you can select your new content type, as is shown in this screenshot:
The issue we have here is that you also need to specify the list type. In some cases, our custom list is not available in the dropdown. I was unable to figure out why the custom list template was not available in the dropdown, but I have seen it a few times. The way to solve this is to full configure your web part. Then save the changes. This will result in the message ‘This query has returned no items’. Export your web part and delete it from the page. Open the file in an editor. Find the property where that attribute Name has value ‘ServerTemplate’. Change the value of this property to the value of your Type attribute in the ListTemplate regristration in Step 6. In my case this is 90010.
1: <webParts> 2: <webPart xmlns="http://schemas.microsoft.com/WebPart/v3"> 3: <data> 4: <properties> 5: <property name="ServerTemplate" type="string">90010</property> 6: </properties> 7: </data> 8: </webPart> 9: </webParts>
Save the .webpart file , import it onto your page and that’s it. Your web part will show the results from your custom lists:
This ZIP file contains all the files I described in this article. http://www.tonstegeman.com/Blog/Documents/TST.TestList2007.zip 12/23/2009I have written some weblog articles (ECMAScript and Managed models) and an article for the Dutch .NET Magazine on the new SharePoint 2010 client object models. Last week I did a presentation for the SDE event of the SDN on this subject. While presenting the Silverlight demo described in this blog post, one of the attendees was paying more attention than I did when writing the code. When I explained the code snippet below, the question was why I was using ExecuteQuery and I just explained the Silverlight API is asynchronous. 1: ClientContext context = ClientContext.Current; 2: Web web = context.Web; 3: String listName = "CommentsList"; 4: ListCollection lists = web.Lists; 5: IEnumerable<List> resultLists = context.LoadQuery(lists.Where(list => list.RootFolder.Name == listName)); 6: context.ExecuteQuery(); 7: List commentsList = resultLists.FirstOrDefault(); 8: if (commentsList != null) 9: { 10: CamlQuery camlQuery = new CamlQuery(); 11: camlQuery.ViewXml = "<View><Query><Where><IsNotNull><FieldRef Name='City'/></IsNotNull></Where></Query></View>"; 12: _comments = commentsList.GetItems(camlQuery); 13: context.Load(_comments, 14: items => items.Include( 15: item => item.Id, 16: item => item["Title"], 17: item => item["Date"], 18: item => item["City"])); 19: 20: ClientRequestSucceededEventHandler success = new ClientRequestSucceededEventHandler(SuccesHandler); 21: ClientRequestFailedEventHandler failure = new ClientRequestFailedEventHandler(FailureHandler); 22: context.ExecuteQueryAsync(success, failure); 23: }
In the presentation I did not have a very good answer to that question (other than ‘uhhhmmmm’). Time to sort it out and get a better answer. This MSDN article describes the asynchronous way of querying SharePoint 2010. This is why I am using this method to submit the CAML query. To get a reference to the SharePoint list, I copied a code snippet from another demo. I am using the RootFolder name to find the list that I want to use, and therefore I need an extra call to SharePoint. I could have used Lists.GetByTitle instead. So I am using an extra roundtrip to SharePoint, in a synchronous call, to get the reference to the list. So apparently the Silverlight client object model is also synchronous. After some more digging in the SDK I found the answer in the article Client Context as Central Object. This is a quote from that page:
“The Silverlight client object model provides both an ExecuteQuery() method, which can be called synchronously from threads that do not modify the user interface (UI), and an asynchronous ExecuteQueryAsync() method for cases where threads do modify the UI.”
Conclusion is the Silverlight client object model can handle both scenarios. If your code does not modify the UI directly, you can use the synchronous method. If your application directly updates the interface, you need to use ExecuteQueryAsync. In my application the snippet above is running in a background thread that does not update the UI, so both methods work. There was no real need to use the asynchronous method.
Thanks for paying attention in the session and asking the question! 12/22/2009
In a previous blogpost I wrote about the Silverlight application I created to show the location of people posting comments on my blog. These locations were displayed as red dots on a Bing Maps map. In this solution I am using:
- SharePoint 2010
- .NET Managed Client Object Model
- Silverlight Client Object Model
- Silverlight 3 Bing Maps control
This post describes how I created it. The source code is available in this ZIP file.
Step 1 – Populate the data
First thing I did was the import of the data into a custom SharePoint 2010 list, using the .NET Managed Client Object Model. I exported the comments in SharePoint 2007 to a CSV file and imported them into a list in SharePoint 2010. The code to do that is shown in the snippet below.
1: int count = 0; 2: using (ClientContext context = new ClientContext("http://tst.demo/sites/team/Comments")) 3: { 4: Web web = context.Web; 5: ListCollection lists = web.Lists; 6: IEnumerable<List> resultLists = context.LoadQuery(lists.Where( 7: list => list.RootFolder.Name == "Comments" 8: )); 9: context.ExecuteQuery(); 10: List commentsList = resultLists.FirstOrDefault(); 11: if (commentsList != null) 12: { 13: String[] lines = System.IO.File.ReadAllLines("C:\\export.csv"); 14: 15: for (int i = 0; i < lines.Length; i++) 16: { 17: ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation(); 18: ListItem companyItem = commentsList.AddItem(itemCreateInfo); 19: String[] columns = lines[i].Split(new String[] { ";" }, StringSplitOptions.None); 20: companyItem["Title"] = columns[1]; 21: DateTime created = DateTime.MinValue; 22: if (DateTime.TryParse(columns[0], new System.Globalization.CultureInfo(1043), System.Globalization.DateTimeStyles.AssumeLocal, out created)) 23: { 24: companyItem["Date"] = created; 25: } 26: companyItem["City"] = columns[2]; 27: companyItem.Update(); 28: count++; 29: } 30: context.ExecuteQuery(); 31: } 32: } 33: MessageBox.Show(String.Format("Imported {0} items", count));
First thing after setting up the ClientContext (based on the URL of the site) to to get a reference to the list. I am using the LoadQuery method of the ClientContext object to get this list. This method allows us to load objects based on a LINQ query. The application tries to find the list based on the name of the rootfolder. Please note this is Linq to Objects, not Linq to SharePoint. Linq to SharePoint is a server side only technology, and we are creating a client application to upload the data. Instead of this way to set up the context, we could have used ClientContext.Current to get the context. If we use this, the SharePoint list must be in the same SharePoint context as the Silverlight application. For every line found in the CSV file, we add a new item to the list, using the ListItemCreationInformation object. Remember that these list items will be added to SharePoint, once ExecuteQuery gets called. This is nice, because we don’t need a roundtrip to the server for every listitem. In my case, I am uploading some 500 list items in one batch. When I first ran this application, it threw an exception of type Microsoft.SharePoint.Client.ServerException. The message of this exception is The request uses too many resources. Apparently a batch of 500 items is too big. I ran the application in batches of 100 items, and that was no problem.
Step 2 – Get the data from SharePoint in Silverlight
Second step is to get the data from the SharePoint list into custom “Comment” objects. After creating the Silverlight application in Visual Studio 2010 (use Silverlight 3!), I added references to the SharePoint Silverlight client assemblies. These are ‘Microsoft.SharePoint.Client.Silverlight.dll’ and ‘Microsoft.SharePoint.Client.Silverlight.Runtime.dll’ and they can be found in folder <SharePoint Root>\TEMPLATE\LAYOUTS\ClientBin. My Silverlight application is created using the out of the box Visual Studio Silverlight application template. This automatically gives you a Home tab, with Home.xaml. In the code behind of Home.xaml, the method ‘OnNavigatedTo’ is implemented. The code is shown below:
1: protected override void OnNavigatedTo(NavigationEventArgs e) 2: { 3: demoMap.Mode = new Microsoft.Maps.MapControl.AerialMode(); 4: _applicationId = ((Microsoft.Maps.MapControl.ApplicationIdCredentialsProvider)demoMap.CredentialsProvider).ApplicationId; 5: ThreadPool.QueueUserWorkItem(new WaitCallback(LoadMapData)); 6: }
The data is loaded using the method LoadMapData (on a different thread). If you don’t run the code that loads the data on a different thread, your application will raise a InvalidOperationException. The message you will get is: The method or property that is called may block the UI thread and it is not allowed. The code snippet below shows the method LoadMapData. This method generates a CAML query and loads the data from SharePoint. CAML is still around in SharePoint 2010. It is the only way in the client object models to query your lists.
1: private void LoadMapData(Object stateInfo) 2: { 3: ClientContext context = ClientContext.Current; 4: Web web = context.Web; 5: String listName = "CommentsList"; 6: ListCollection lists = web.Lists; 7: IEnumerable<List> resultLists = context.LoadQuery(lists.Where(list => list.RootFolder.Name == listName)); 8: context.ExecuteQuery(); 9: List commentsList = resultLists.FirstOrDefault(); 10: if (commentsList != null) 11: { 12: CamlQuery camlQuery = new CamlQuery(); 13: camlQuery.ViewXml = "<View><Query><Where><IsNotNull><FieldRef Name='City'/></IsNotNull></Where></Query></View>"; 14: _comments = commentsList.GetItems(camlQuery); 15: context.Load(_comments, 16: items => items.Include( 17: item => item.Id, 18: item => item["Title"], 19: item => item["Date"], 20: item => item["City"])); 21: 22: ClientRequestSucceededEventHandler success = new ClientRequestSucceededEventHandler(SuccesHandler); 23: ClientRequestFailedEventHandler failure = new ClientRequestFailedEventHandler(FailureHandler); 24: context.ExecuteQueryAsync(success, failure); 25: } 26: }
As you see in the example, the query that gets the data from SharePoint is asynchronous. Instead of calling ExecuteQuery, we use ExecuteQueryAsync. This method takes 2 parameters, the eventhandlers that are called when the method completes. There is one handler for successful completion and one in case an exception occurred. The sample below shows my success event handler.
1: private void SuccesHandler(object Sender, ClientRequestSucceededEventArgs e) 2: { 3: List<Comment> comments = new List<Comment>(); 4: foreach (ListItem item in _comments) 5: { 6: Comment comment = new Comment(); 7: if (item["Title"] != null) 8: comment.Title = item["Title"].ToString(); 9: if (item["City"] != null) 10: comment.City = item["City"].ToString(); 11: if (item["Date"] != null) 12: comment.Date = (DateTime)item["Date"]; 13: comments.Add(comment); 14: } 15: if (comments.Count > 0) 16: { 17: for (int i = 0; i < comments.Count; i++) 18: { 19: GetCity(comments[i].City, i); 20: } 21: } 22: }
This snippet iterates through the listitem collection in the result set and creates a new Comment object for every item. For every comment, the method GetCity is called.
Step 3 – Add a Bing Maps control to your XAML
To add the Bing Maps control to your XAML, you need to follow these steps. Chris Pietschmann has a good article that will get you started.
- Install the Bing Maps Silverlight Control SDK (presuming you have everything in place for Silverlight 3 development)
- Add project references to the “Microsoft.Maps.MapControl.dll” and “Microsoft.Maps.MapControl.Common.dll” assemblies.
- Set up a Bing Maps developer account.
- Add XAML to your application
The XAML to add the Bing Maps control to my application is shown below:
1: <navigation:Page x:Class="TST.SDE20091214.SilverlightComments.Home" 2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4: xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 5: xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl" 6: xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation" 7: mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480" 8: Title="Home" 9: Style="{StaticResource PageStyle}"> 10: 11: <Grid x:Name="LayoutRoot"> 12: <ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}"> 13: 14: <StackPanel x:Name="ContentStackPanel"> 15: 16: <TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}" 17: Text="Demo using Silverlight 3 Bing Maps control"/> 18: <TextBlock x:Name="ContentText" Style="{StaticResource ContentTextStyle}" 19: Text="Ton Stegeman"/> 20: <m:Map Height="800" Name="demoMap" CredentialsProvider="INSERT_YOUR_ID"> 21: </m:Map> 22: </StackPanel> 23: 24: </ScrollViewer> 25: </Grid> 26: 27: </navigation:Page>
In the attribute CredentialsProvider of the Map control you need to add your Bing Maps key. Next thing to do is to implement the GetCity method that plots the city on the map.
Step 4 – Find the cities on the map
The method GetCity finds the cities using a Bing Maps webservice and plots them on the map. First thing to do is get a service reference to the Bing Maps service. Add a service reference to the http://dev.virtualearth.net/webservices/v1/geocodeservice/GeocodeService.svc and give it a name. In my case it is called GeocodeService. After setting up the request, you need to pass the credentials. This is your Bing Maps key, the CredentialsProvider of the Map control in the XAML. In the OnNavigatedTo method in Step 2, you will find the private member _applicationId is set to the ApplicationId. This Id is saved in the private member, because the code that uses it is running in a different thread in which we cannot get access to the user interface components.
1: private void GetCity(string city, int wayPointIndex) 2: { 3: if (String.IsNullOrEmpty(city)) 4: return; 5: GeocodeService.GeocodeServiceClient geocodeService = new GeocodeService.GeocodeServiceClient("BasicHttpBinding_IGeocodeService"); 6: geocodeService.GeocodeCompleted += new EventHandler<GeocodeService.GeocodeCompletedEventArgs>(geocodeService_GeocodeCompleted); 7: GeocodeService.GeocodeRequest geocodeRequest = new GeocodeService.GeocodeRequest(); 8: 9: Credentials cred = new Credentials(); 10: cred.ApplicationId = _applicationId; 11: 12: geocodeRequest.Credentials = cred; 13: geocodeRequest.Query = city; 14: geocodeService.GeocodeAsync(geocodeRequest, wayPointIndex); 15: }
The snippet above uses the ‘city’ passed as parameter as the query variable. The Bing Maps service is asynchronous, and we call the service using the GeocodeAsync method. When this method completes successfully, the GeocodeCompleted event is raised. This is implemented as in the snippet below.
1: private void geocodeService_GeocodeCompleted(object sender, GeocodeService.GeocodeCompletedEventArgs e) 2: { 3: _count++; 4: int waypointIndex = System.Convert.ToInt32(e.UserState); 5: 6: if (e.Result != null && e.Result.Results.Count > 0) 7: { 8: Dispatcher.BeginInvoke(() => 9: { 10: foreach (GeocodeService.GeocodeResult result in e.Result.Results) 11: { 12: Location location = new Location(result.Locations[0].Latitude, result.Locations[0].Longitude); 13: Ellipse point = new Ellipse(); 14: point.Width = 10; 15: point.Height = 10; 16: point.Fill = new SolidColorBrush(Colors.Blue); 17: point.Opacity = 0.65; 18: MapLayer.SetPosition(point, location); 19: MapLayer.SetPositionOrigin(point, PositionOrigin.Center); 20: demoMap.Children.Add(point); 21: } 22: }); 23: } 24: }
Please note that this handler is not running on the UI thread, and therefore we need to call Dispatcher..BeginInvoke() before updating the UI.
Step 5 – Test the application
Last step is to test the application. There are several ways to deploy a Silverlight application, and there are pros and cons for every solution. I have uploaded the XAP file to a document library. On my test page I am using the new Silverlight Web Part of SharePoint 2010 (Media and Content category).
Hope you like the solution and it gives you some ideas to get started with SharePoint 2010 and Silverlight. Using the new client object model it is very easy to get your SharePoint data into the Silverlight application. Full source code is available in this ZIP file. 12/2/2009On my weblog you need to enter the name of the city you live in when you add a comment. I started this to prevent spammers overloading my weblog comments. Works pretty well and as there were getting more and more comments, I thought it would be nice to plot them all on a map, to see where everybody is from. The screenshot below shows that output. My Silverlight application looks like this. Now I know where you all are :-): I created it by importing the comments into a SharePoint 2010 list. I created a Silverlight 3 application using the new Bing Maps Silverlight control. To get the data from SharePoint, I am using the Silverlight client object model. Nice thing is that zoom works, and the map mode works, so I can zoom into any region. After cleaning up the code, I will post how I have built this. I like the result so much, that I couldn’t stop myself from posting it :-).
|
|
|
|
|
|
|
|