 |
|
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. |
|
|
|
|
|
|
 |
|
|
|
|
|
|
|
|
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/2010Introduction 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 :-). 11/23/2009The new Team Foundation Server (currently in beta 2) has the option to connect TFS project to an existing SharePoint site. This also allows you to specify on which SharePoint server to create new project sites. For us this is important, because a lot of our projects start way before the TFS project gets created. Then we end up moving all files to the TFS project site, or we have two project sites for the same project. When we go live with TFS2010, we no longer have to transfer files. And we can host the TFS project sites on our company SharePoint implementation, which means 1 WSS server less to maintain. With one of my colleagues we managed to get this working in our test environment. This post shows you how we have set it up. Step 1 – Configure SharePoint site setup TFS has two options for linking TFS projects to SharePoint sites, and it is important to think about this before you start configuring: - Automatically – if you create a new TFS project, TFS will automatically create the new SharePoint site for you. This will be a sub site in the site collection that you specify for for each Team Project Collection in TFS. This collection is a group of TFS projects. For each new project, a new subsite is created in this site collection. If this site collection is using a managed path, you will have to remember this managed path.
Apart from the managed path (in case you are using that) you need to remember the web application that hosts your site collection(s) and the service account that runs the application pool of this web application. You can also configure TFS to automatically create the site collection that is associates to your project collection (make sure self service site creation is switched on!). We have not yet tested this setup. - Manually – You are manually creating SharePoint project sites and your TFS projects and after both are created, you manually connect them from TFS. If this is the case, you will need to write down all the SharePoint application URL that will host project sites, including their application pool accounts. If these SharePoint sites are using managed paths, you need to write down these as well.
We will go for the second option, as we want some more control over where the SharePoint sites are created. Apart from that, they probably already created before the TFS project is created. The samples below show our very simple test setup. We have configured 1 web application, with 1 managed path (tfsprojecten). We will manually create a site collection for each project at the url http://<web_application_url>/tfsprojecten, and connect the TFS project later on to the root site of that site collection. Step 2 – Configure TFS connection to SharePoint In this step we will configure which SharePoint service accounts have access to TFS. On the TFS server, start the Team Foundation Server Administration Console. In the Application Tier select SharePoint Web Applications. In the lower section of the screen, “Service Accounts for SharePoint Web Applications”, add the accounts for 2 web applications: - The Central Administration web application
- The web application that will host your project sites (can be accounts of multiple web applications)
The accounts that you need to add here are the accounts of the application pools that are associated with these web applications: For the next step, we stay in the console on the same screen. In the SharePoint Web Applications section, click Add SharePoint Web Application: Enter a friendly name for the web application, that makes it easy to recognize. Fill in the url of the web application (step 1) and the url of the SharePoint Central Administration site. If you have your site collection(s) using a managed path in SharePoint, enter this managed path in the Default location textbox. Step 3 – Configure SharePoint connection to TFS Before we configure the connection from SharePoint to Team Foundation Server, make sure the Team Foundation Server Extensions for SharePoint Products are installed in the SharePoint farm. See the installation guide for more details. On the SharePoint server, start Team Foundation Server Administration Console. Select Extensions for SharePoint Products. Click the Grant access button to setup a new connection. Enter the url to your TFS server and select a web application. If you have configured a managed path in SharePoint and you want to enforce TFS to create all new site collections for new project collections in that path, enter your path here as well. Step 4 – Configure the project collection(s) If you have already added created project collections in TFS, you need to configure the connection for each collection to SharePoint. On the TFS server, start the Team Foundation Server Administration Console. Select Team Project Collections and select your collection. Navigate to the SharePoint Site tab. Click the Edit Default Site Location button. Select your web application (recognizable from the friendly name you configured in step 2). Check if TFS configured the url for your site collection correctly. If you configure the connection manually, this has to be an existing site collection, and the url that TFS suggests here (based on your input from previous steps), must match the url of that site collection. If the url does not match, you can manually change the path here. If you want TFS to do the work for you, you are done now. As soon as you add a new TFS project to the project collection, TFS will create the new project and the wizard will ask you for the required details: If the url that the Team Explorer wizard proposes here does not match your SharePoint site collection, you can correct it here, or check the previous steps if you have missed one. If you did not yet create a project collection, or want to create a new one, you should also be ready now. As soon as you create a new collection from the console, the wizard has an extra step SharePoint Site. In this step a new site collection gets created in the web application that you select from the dropdown (again, recognizable from the friendly name): Step 5 – Configure the connection manually In our case we wanted to configure a TFS project to an existing SharePoint site. You can manually configure this connection from Team Explorer. Right click the project, select Team Project Settings and select Portal Settings. Next, check the Enable team project portal checkbox in the popup window. Then click the Configure URL button. In the popup windows that appears, select the web application that hosts your SharePoint site. If the url that is suggested here does not match the url of your SharePoint site, make sure you did not miss one of the previous steps, or manually change it. Step 6 – Configure your SharePoint site This step is only relevant if you manually connected your TFS project to your SharePoint sites. If TFS created your team site, the site is already configured based on the template. If you successfully connected your TFS project and SharePoint team site, you can start using web parts from the new Visual Studio Team System Web Part Collection. To use these web part, active this site collection feature: Now you can use these web parts on any SharePoint page in your site. Simply add one or more web parts to a page and configure them. The screenshot below shows the home page of a Collaboration Portal that is configured as the project site for our test project in TFS. This is not something that I would recommend or would do in our production environment once we migrate to 2010, but it shows that the integration of TFS in SharePoint is built very nicely using web parts, which makes it very flexible. The web part collection has a number of different types of web parts. The screenshot below shows an overview. These webpart are pretty clever. They ‘know’ the connection to the team foundation server themselves. If you add the Team System Web Access Shortcut Web Part for example, you do not need to configure it. It automatically finds the connection to the TFS server and renders the correct link to TSWA. Nice! Other web part, like the Query Results Web Part need some additional configuration. Using the Query Picker, you select one of the queries on that’s it. You have a web part in your site showing all your open tasks for example: Conclusion Hope this post helps to clarify a bit how SharePoint 2007 and TFS 2010 will integrate. I would recommend playing around with this in a lab environment to find the best option for your organization. Put a TFS administrator and an experienced SharePoint administrator in the team to set this up, because together these people should be able to figure out how to set it up. We have done this and are enthusiastic about the options this gives us in the near future! Starting point for your journey should be the MSDN documentation. 11/18/2009SPDatabase is a class that encapsulates access to SQL Server databases. For my new CodePlex project I wanted to add a new database to SQL Server. The issue I was having was how to create this new database in SQL Server and manage access to it without having to store another connection string. Someone pointed me to the SPDatabase class that was supposed to be able to do this. I decided to figure it out and this post describes how I added a new database using this object. In my project I have created a database setup page. This page can be accessed by farm administrators in the Central Admin site: This menu option is added to the Application Management section by activating a site collection feature in the Central Admin Site. The first time an admin clicks the link, the page will find out the database does not yet exist and it will display as shown in the screenshot below: The bottom of the page contains a Create button and when clicked, it will create and provision the database. After provisioning it will register version number 1 of the database. The page will then look like this: The page shows the database server that hosts the new database and its name. It also shows when the database was created, by who this was done and the current version of the database schema. This is a custom (very simple) versioning system that I implemented in the database. The database setup page will automatically check if the current schema is up to date. If it is not, it will notify the administrator as shown in this screenshot: In this blogpost I will describe the most relevant parts of this database setup and upgrade process. The page also has the option to delete the database. Step 1 – Create your own database object First thing to do is create your own database object. Your project in Visual Studio needs a reference to Microsoft.SharePoint.dll. Add a new class to your Visual Studio project and add Microsoft.SharePoint.Administration to the using statements. You can now inherit your class from SPDatabase. Add the 2 constructors to your object that are shown in the code sample below: 1: public class SharePointObjectsDatabase: SPDatabase 2: { 3: private VersionInfo _versionInfo; 4: 5: public SharePointObjectsDatabase() 6: { 7: _versionInfo = new VersionInfo(this); 8: } 9: 10: public SharePointObjectsDatabase(String componentName, SPDatabaseServiceInstance databaseService) : 11: base(componentName, databaseService) 12: { 13: _versionInfo = new VersionInfo(this); 14: } 15: }
These constructors create a new VersionInfo object. See step 2 for more details. Next thing to do is to override the Provision method. In the implementation you need to tell the SPDatabase where to find the SQL file with the setup script of your database. My provision method looks like this:
1: public override void Provision() 2: { 3: base.Provision(); 4: FileInfo sqlFile = GetProvisioningSQL(); 5: if (sqlFile != null) 6: { 7: Dictionary<string, bool> options = new Dictionary<string, bool>(); 8: Provision(this.DatabaseConnectionString, sqlFile.FullName, options); 9: SqlConnection.ClearPool(new SqlConnection(this.DatabaseConnectionString)); 10: _versionInfo.RegisterVersion(1); 11: } 12: }
The file containing my initial database scripts is loaded by the function GetProvisioningSQL. After calling the Provision method of the SPDatabase and passing the name of the file and the connection string (again, automatically managed by our database object), all existing connections to the new database are closed. After these connections are closed, the database schema version 1 is registered in the database by calling RegisterVersion. The code to load the SQL is shown below. This function finds the feature definition of the site collection feature that registers the menu item in the Central Administration site. The feature folder for this feature also contains the provisioning SQL Script for my database.
1: private FileInfo GetProvisioningSQL() 2: { 3: foreach (SPFeatureDefinition feature in SPFarm.Local.FeatureDefinitions) 4: { 5: if (feature.Id == new Guid(SharePointObjectsMenuFeatureID)) 6: { 7: DirectoryInfo dir = new DirectoryInfo(feature.RootDirectory); 8: if (dir.Exists) 9: { 10: string mask = @"CreateDatabase.sql"; 11: FileInfo[] files = dir.GetFiles(mask, SearchOption.AllDirectories); 12: if (files.Length == 1) 13: { 14: return files[0]; 15: } 16: } 17: } 18: } 19: return null; 20: }
If the feature definition is found, the code will try to find a file in the feature folder called “CreateDatabase.sql”. This file contains all SQL scripts for the initial provisioning of my database. If nothing is found, or multiple files with that name are found, nothing gets provsioned and we end up with an empty database.
Step 2 – Create the versioning object
In the first step we will create the versioning object. Nothing special here, it just is an object that knows how to read the database schema version from that database once it is created. It also knows how to register a version information for a new version of the database schema.
1: public class VersionInfo 2: { 3: private int _version; 4: private string _createdBy; 5: private DateTime _created; 6: private SharePointObjectsDatabase _database; 7: 8: internal VersionInfo(SharePointObjectsDatabase database) 9: { 10: _database = database; 11: } 12: 13: internal int DatabaseVersion 14: { 15: get 16: { 17: if (_version == 0) 18: GetVersionInfo(); 19: return _version; 20: } 21: } 22: 23: internal string CreatedBy 24: { 25: get 26: { 27: if (_createdBy == null) 28: GetVersionInfo(); 29: return _createdBy; 30: } 31: } 32: 33: internal DateTime Created 34: { 35: get 36: { 37: if (_created == null) 38: GetVersionInfo(); 39: return _created; 40: } 41: } 42: 43: internal void GetVersionInfo() 44: { 45: // Omitted for brevity 46: // Call stored procedure to read versioninfo and set properties 47: } 48: 49: internal void RegisterVersion(int version) 50: { 51: // Omitted for brevity 52: // Call stored procedure to register new version info 53: } 54: 55: }
This example does not contain the code for the SQL Server access. This is very common .NET code and therefore not interesting. The only interesting bit is the connection string to the database. In the functions GetVersionInfo and RegisterVersion, I use the connectionstring that is available on our custom database object:
1: SqlConnection connection = new SqlConnection(_database.DatabaseConnectionString);
The connection string used is the value of the DatabaseConnectionString property of our custom SPDatabase object that we created in the previous step. This is all managed by the SPDatabase itself. I see this as a big advantage of using this object. I no longer have to worry about connection strings and getting the correct connection string applied when my project moves across different servers towards production.
Step 3 – Initialize the database setup page
Next thing to do is to create the database setup page that is shown in the screenshots above. I won’t bore you with the HTML of this page. In the Onload, the status of our database is checked and the page is updated accordingly.
1: private void InitUI() 2: { 3: _action = string.Empty; 4: labelVersion.Text = "N/A"; 5: labelCreated.Text = "N/A"; 6: labelCreatedBy.Text = "N/A"; 7: labelDatabase.Text = "N/A"; 8: labelServer.Text = "N/A"; 9: if (Database == null) 10: { 11: buttonCreatePage.Text = "Create"; 12: _action = "create"; 13: labelStatus.Text = "Database does not exist. Click the 'Create' button to create the database."; 14: labelStatus.ForeColor = System.Drawing.Color.Red; 15: labelStatus.Font.Bold = true; 16: } 17: else if (!Database.Exists) 18: { 19: buttonCreatePage.Text = "Configure"; 20: _action = "configure"; 21: labelStatus.Text = "Database has not been configured. Click the 'Configure' button to configure the database."; 22: labelStatus.ForeColor = System.Drawing.Color.Orange; 23: labelStatus.Font.Bold = true; 24: } 25: else if (Database.NeedsUpgrade) 26: { 27: buttonCreatePage.Text = "Upgrade"; 28: _action = "upgrade"; 29: labelStatus.Text = "Database needs an upgrade. Click the 'Upgrade' button to upgrade the database to the latest version."; 30: labelVersion.Text = Database.VersionInfo.DatabaseVersion.ToString(); 31: labelCreated.Text = Database.VersionInfo.Created.ToString(SPContext.Current.Web.Locale); 32: labelCreatedBy.Text = Database.VersionInfo.CreatedBy; 33: labelStatus.ForeColor = System.Drawing.Color.Orange; 34: labelStatus.Font.Bold = true; 35: } 36: else 37: { 38: buttonCreatePage.Text = "OK"; 39: _action = "close"; 40: labelStatus.Text = "Database is up to date. No further action required."; 41: labelVersion.Text = Database.VersionInfo.DatabaseVersion.ToString(); 42: labelCreated.Text = Database.VersionInfo.Created.ToString(SPContext.Current.Web.Locale); 43: labelCreatedBy.Text = Database.VersionInfo.CreatedBy; 44: labelStatus.ForeColor = System.Drawing.Color.Green; 45: labelStatus.Font.Bold = true; 46: } 47: if (Database != null) 48: { 49: labelDatabase.Text = Database.Name; 50: labelServer.Text = Database.Server.Name; 51: } 52: }
The function InitUI checks whether or not the value of the property Database is assigned:
1: protected SharePointObjectsDatabase Database 2: { 3: get 4: { 5: if (_database == null) 6: { 7: _database = SharePointObjectsDatabase.GetDatabase(); 8: } 9: return _database; 10: } 11: set 12: { 13: _database = value; 14: } 15: }
The getter of this property calls the GetDatabase method of the database object that we created in the previous step. The implementation of the GetDatabase method is shown below:
1: internal static SharePointObjectsDatabase GetDatabase() 2: { 3: SPAdministrationWebApplication adminWeb = SPAdministrationWebApplication.Local; 4: foreach (SPDatabase db in adminWeb.WebService.DefaultDatabaseInstance.Databases) 5: { 6: if (db is SharePointObjectsDatabase) 7: { 8: return (SharePointObjectsDatabase)db; 9: } 10: } 11: return null; 12: }
This code iterates through all databases of the DefaultDatabaseInstance that is associated with the Central Administration web application. If it finds a database of the correct type, e.g. it is created as database object of the class we created in step 1, it returns that database object.
Step 4 – Create the database
If our database does not yet exist, our database setup page will create and provision it. The code snippet below is copied from the event handler that handles the click event on the Create button:
1: Database = SharePointObjectsDatabase.Create(); 2: Database.Update(); 3: if (!Database.Exists) 4: { 5: Database.Provision(); 6: }
The Update and Exists methods in the SPDatabase object, so we don’t have to worry about that. We have seen our custom implementation of the Provision method before, so the code here is very easy and simple. This snippet calls the static create method of our custom database object:
1: internal static SharePointObjectsDatabase Create() 2: { 3: SPAdministrationWebApplication adminWeb = SPAdministrationWebApplication.Local; 4: return new SharePointObjectsDatabase(DATABASETITLE, adminWeb.WebService.DefaultDatabaseInstance); 5: }
The DATABASETITLE constant string contains the name of the database. In my case this is ‘SharePoint Objects database’. This is the name of the database that will be displayed in the UI and it is the name of the database in SQL Server.
Step 5 – Upgrade the database
First we need to check whether or not our database needs an upgrade. To do this, we override the NeedsUpgrade property of SPDatabase:
1: public override bool NeedsUpgrade 2: { 3: get 4: { 5: List<FileInfo> sqlFiles = GetUpgradeSQL(); 6: return sqlFiles.Count > 0; 7: } 8: set 9: { 10: base.NeedsUpgrade = value; 11: } 12: }
Our new implementation loads a list of SQL files that contain upgrade scripts. These are loaded by the method GetUpgradeSQL:
1: private List<FileInfo> GetUpgradeSQL() 2: { 3: List<FileInfo> result = new List<FileInfo>(); 4: foreach (SPFeatureDefinition feature in SPFarm.Local.FeatureDefinitions) 5: { 6: if (feature.Id == new Guid(SharePointObjectsMenuFeatureID)) 7: { 8: DirectoryInfo dir = new DirectoryInfo(feature.RootDirectory); 9: if (dir.Exists) 10: { 11: string mask = @"UpgradeDatabase_*.sql"; 12: FileInfo[] files = dir.GetFiles(mask, SearchOption.AllDirectories); 13: foreach (FileInfo file in files) 14: { 15: if (GetVersionForUpgradeFile(file) > VersionInfo.DatabaseVersion) 16: { 17: result.Add(file); 18: } 19: } 20: } 21: } 22: } 23: return result; 24: }
This method tries to find the feature definition of the feature that installed the menu item for the database setup page. Just like in step 1, the code checks this folder and its sub folder for presence of SQL files. The naming convention for upgrade files is ‘UpgradeDatabase_<version>.sql’. The upgrade SQL file for the schema upgrade to version 4 is called ‘UpgradeDatabase_4.sql’. It compares the version number in the name of this file with the current database schema version registered in the database. If the file version is bigger, the SQL file is returned. If NeedsUpgrade finds SQL files that contains SQL script that was not yet handled, the Upgrade action is started when the user clicks the Upgrade button:
1: if (Database.NeedsUpgrade) 2: { 3: Database.Upgrade(); 4: }
The Upgrade method of our database is another overridden method of SPDatabase:
1: public override void Upgrade() 2: { 3: base.Upgrade(); 4: List<FileInfo> sqlFiles = GetUpgradeSQL(); 5: if (sqlFiles != null) 6: { 7: foreach (FileInfo file in sqlFiles) 8: { 9: StreamReader reader = File.OpenText(file.FullName); 10: string sql = reader.ReadToEnd(); 11: reader.Close(); 12: RunSQLFile(sql); 13: VersionInfo.RegisterVersion(GetVersionForUpgradeFile(file)); 14: } 15: } 16: }
This code simply iterates through all upgrade SQL files that were found. It gets the upgrade version number from the file name, runs the upgrade script and registers the new version information in the database. This way the version information table in the database always can tell us when we upgraded to which version.
Step 6 – Delete the database
This last step is very simple. Because our SharePoint knows everything about our database, we just need to unprovision and delete it:
1: if (Database.Exists) 2: { 3: Database.Unprovision(); 4: Database.Delete(); 5: }
This snippet is taken from the eventhandler associated with the Delete button.
Conclusion
As we have seen in this post, creating a database in your SharePoint farm can be easily done using the SPDatabase object. You still find a lot of code in this post, but most of it is to make to maintenance page work correctly and to implement the upgrade scenario. If you just need to deploy a database without having to worry about versioning of the database schema, you just need a few lines of code. Another big advantage of using SPDatabase is that you no longer need to worry about connection strings. And because we inherit our database from SPDatabase, we can also have our database in the SharePoint backup of the content databases. 11/5/2009On my CodePlex project I have just released a new version of the Content By Type web part and the Filter webparts. This post will summarize the changes in the web parts. It will also introduce the latest family member, the User Information Filter web part. I have introduced the Content By Type web parts two years ago, and some time later I introduced the filter web parts to the package. Currently the web part package has been downloaded nearly 3000 times and I get some great feedback en feature requests. So it was time for a new version. I want to thank Jesus Lopez from Spain for creating a number of nice new features. Thank you very much! User Info Filter Web Part This web part is new in this release of the package. It can get a value from the User Information List from the current user and pass that as a filter value to connected web parts. The screenshot below shows an example. It shows the page in edit mode. The User Info Filter web part is a context filter web part and therefore is not visible at runtime. In this example, it is used twice. The upper web part gets and name of the logged on user and passes that to the connected My News web part. This is a Content By Type web part that queries the site collection for news items published by myself. The second User Info Filter gets the department for the current user and passes that to the My department news web part. This shows an overview of all news published by my department. This new filter web part is available both in WSS and MOSS. Besides being a filter provider, the User Info Filter web part also is a filter consumer. This way you can load properties of another user from the User Information List in SharePoint. Another filter web part on the page provides the name (or any other user property) to the User Info Filter. This web part finds that user in the User Information list and passes one of the values of that user to the consumers. The screenshot below shows an example. In this screenshot you see a List Item Filter web part. This lets users pick an author from a list. The name of this author is passed as filter value to a User Info Filter web part and a Content By Type web part. The user info filter web part load the department for the selected user and sends that value to another Content By Type web part. These two news web parts aggregate the news written by that user, or written by the department of that user. Content By Type web part This paragraph shows all new features of release 1.3 of the Content By Type web part: - Filter using column headers
Just like any List View web part in SharePoint, you can now filter the Content By Type content by selecting one of the values in one of the column headers.
- If you use grouping, you now have an option to hide the fieldname from the group header.
- The context menu can show the option to take users directly to the folder that contains the item / document. Instead of redirecting you to the list and making you find the folder yourself, the web part now directly redirects you to the folder.
- While configuring the web part, you decide what options appear in the context menu. Instead of all options, you can select what options will be available for your users.
- Links to display and edit forms now pass the current page as Source parameter in the querystring. Users are redirected back to your overview page when they click the Close button.
- There is a new option to collapse all groups by default.
- An item in the list can now be linked directly to either the item, the folder, the list or the site. In previous versions you could only link to the item directly.
Item Filter web part There have been a number of enhancements to the list item filter web part: - Improvements have been made to the loading mechanism of the list. You enter the server relative url to the site and the list. You can use the title of the list, or the url name. If you want to use a list from the current site, simply enter the title or url name of the list, without any slashes. In WSS the guidance to enter the reference to the list is improved.
- The tag cloud has the option to display a ´Show all´ link. This link can be used to reset a previously selected filter.
- You can now use multiple webparts that show a tag cloud on the same page. They no longer interfere with each other.
- While configuring the web part, you can now set an item limit for the query.
- If you are using multiple List Item filter web parts on the same page that use the Checkbox display mode, you can hide the search button for all but one.
- Items in sub folders are now also included in the filter web part.
- There has been a bug fix in connecting to and from the web part when used in the Tag Cloud display mode.
- Loading a list from a site collection that is using an ´explicit inclusion´ managed path now works.
Page Column Filter web part This release of the Page Column Filter web part has a number of improvements: - The reference to the selected field is stored in the web part configuration as the internal name of the field, instead of the ID. This makes it easier to use the web part in multi server environments (like developement, test, production)
- The web part can now be used in a page layout directly.
- The web part now also sends values of calculated fields to connected web parts.
Web Context Filter web part - Release 1.3 of the filter web parts makes the Web Context Filter web part available in WSS. Previous versions could only be used in MOSS.
- The web part can now be used in a page layout directly.
The new package is released as version 1.3 on this CodePlex page. Enjoy the new version! 11/3/2009
Update 04-12-2009: In the Beta version, executeQuery is now called executeQueryAsync. Updated the code samples.
As you probably have seen/heard in the news on last week’s SharePoint Conference in Las Vegas, SharePoint 2010 will ship with 3 new object models:
- a managed .NET API for building client applications using a SharePoint object model.
- a managed Silverlight object model.
- an object model to be used in Javascript, the ECMAScript library.
For developers this is an important step forward. Until now, developing SharePoint applications using an object model was limited to applications running on the server. For client applications we had to rely on web services and the RPC protocols. Using these 3 new models, it will be much easier to create client applications. And the reach of our applications will be bigger, because they are very likely to also run on the new SharePoint Online. The three object models are a limited sub set of the server object model. Most objects, properties and methods of the site collection (SPSite) and it’s underlying structures are available in the new models.
In this article I will show you an example of a custom ASPX page that will use the Javascript library. It is a page that runs in the LAYOUTS folder. It shows all users that have direct permissions on the current site. By clicking a user name, the page will show me more information about that user. By selecting a user and one of the available SharePoint groups, the page will add the user to that group. After doing that, the page will remove the permissions for the user. The screenshot below shows the page:
Showing the users and the user groups is done in server side code. Showing user information and moving the user’s permissions to the group is handled by javascript using the ECMAScript library.
Step 1 – Show the users and groups
The ASPX page loads the lists with users and groups in the OnLoad of the page. It creates a collection of custom objects and sets these collections as the datasource for the Repeater controls. The code snippet below shows how the users are loaded:
1: SPWeb web = SPContext.Current.Web; 2: List<SharePointUser> users = new List<SharePointUser>(); 3: foreach (SPRoleAssignment assignment in web.RoleAssignments) 4: { 5: if (assignment.Member is SPUser) 6: { 7: SPUser user = (SPUser)assignment.Member; 8: SharePointUser usr = new SharePointUser(user.ID, user.Name); 9: users.Add(usr); 10: } 11: } 12: listUsers.DataSource = users; 13: listUsers.DataBind();
The snippet below shows the ASPX code for the repeater that shows the users:
1: <asp:Repeater ID="listUsers" runat="server"> 2: <HeaderTemplate> 3: <h3>Users with direct permissions on this site</h3> 4: </HeaderTemplate> 5: <ItemTemplate> 6: <li style="list-style-type:none"> 7: <input type="checkbox" id="selectUser<%# DataBinder.Eval(Container.DataItem, "ID") %>" name="selectUser" /> 8: <a href="javascript:getUserInfo(<%# DataBinder.Eval(Container.DataItem, "ID") %>);"> 9: < %# DataBinder.Eval(Container.DataItem, "Name") %> 10: </a> 11: </li> 12: </ItemTemplate> 13: </asp:Repeater>
For every user in the collection, a hyperlink is rendered. When the link is clicked, javascript function “selectUser” is called. This function takes the ID of the user as parameter
Step 2 – Preparing the page for using the ECMAScript library
The ECMAScript library is available in a number of JS files in the LAYOUTS folder. The main file is SP.js. When you include this file in the APSX page using a ScriptLink control, all other required JS files are loaded automatically. This MSDN page shows all relevant files. By linking SP.js to your page, the SP namespace is registered. This is the SharePoint namespace that contains all objects. If you are going to build code using this library, you will very likely need this MSDN Page to reference what object, properties and methods are available. The SharePoint js files are minified (‘crunched’ in the SDK) versions. This makes them hard to read and use for debugging. Therefore every js file also has a ‘debug’ equivalent in the same folder. By opening sp.debug.js you will be able to see what is available in the SP namespace.
The snippet below show how to prepare the ASPX page:
1: <SharePoint:ScriptLink 2: Name="sp.js" LoadAfterUI="true" Localizable="false" 3: runat="server" ID="ScriptLink1" />
Step 3 – Getting the user information
As we have seen in step 1, clicking a user’s name fires the javascript function ‘selectUser’. This function is shown in the snippet below.
1: function getUserInfo(userID) { 2: var clientContext = new SP.ClientContext.get_current(); 3: var web = clientContext.get_web(); 4: var userInfoList = web.get_siteUserInfoList(); 5: var camlQuery = new SP.CamlQuery(); 6: camlQuery.set_viewXml('<View><Query><Where><Eq><FieldRef Name=\'ID\'/>' + 7: '<Value Type=\'Number\'>' + userID + '</Value></Eq>' + 8: '</Where></Query><RowLimit>1</RowLimit></View>'); 9: this.collListItem = userInfoList.getItems(camlQuery); 10: clientContext.load(collListItem); | | | | | |