Wednesday, June 13, 2012

Configuring an SPDataSource to access data from External Lists tied to External Content Types

The SPDataSource, used in conjunction with External Lists and External Content Types, is a powerful way to access traditional list data in SharePoint.  The SPDataSource is very useful for populating dropdowns, checkbox lists, and other data driven page layout elements.  Using the SPDataSource tied to an External List is very similar to one tied to a traditional SharePoint list, but there are a few gotchas and things you can do to make your life easier.

So, let's walk through a scenario of how we might take data from a database, in our case SQL Server 2008, create an External Content Type (ECT), create an External List (EL) from the External Content Type, and expose the data in that list to and end user in a dropdown that is part of a page layout.

Time for nerd stuff - spinner hats ON!!!


(Disclaimer:  this is not me, but is an epic picture nonetheless.)
First, go to your database, and add a table named "CITIES".  Add a nvarchar(25) field to it called "CITY" that is the primary key.  Then, create a view on that table - something like vwCities, etc.  This view is going to form the foundation for our ECT, and thus our External List (EL).

I advocate tying the ECT to a sql server view for all read operations rather than to the table directly. This is primarily because it is easy to change a view, and even flatten a highly normalized table structure, thus making your external list's implementation much easier to support, tweak, and use in development of page layouts and search. You can change a table out from underneath a view, even rewrite or optimize the view, without having to rebuild your entire BCS model, External Content Types, and External Lists.

Second you need to create your External Content Type (ECT).  We're going to stick with SharePoint Designer for this demo.  Open up your SharePoint site collection's root site, click External Content Types in the left hand panel, then click the new External Content Type button in the ribbon at the top right of the screen.  Configure your external system, make sure you type a name like "City" as the Name of the ECT, then create your operations.  In our scenario, just create a Read List and Read Item method - leave the others out.
I realize I'm glossing over the finer details here, but I am assuming some level of comfort with creating ECTs in this demo.  Naturally using SPD is not a requirement here, and a Visual Studio is often an easier way to create robust BCS models.  We've used SPD here the sake of simplicity and brevity in deployment and configuration.


Once you've created your External Content Type, create the External List. This is easily done in SharePoint Designer 2010 by clicking the "Create List and Forms" button in the ribbon. Unless InfoPath (IP) is a part of your solutions' architecture, don't bother with checking the checkbox to create the IP form. For purposes of data or list structure the IP form is not needed.

Next, you're going to create the SPDataSource object in your page layout. First, you probably put your External List at the top site in the site collection. This is a good practice. You don't want to have to build SPDataSources smart enough to figure out a dynamic path to your list if you can avoid it. You're going to want to add the WebUrl and ListName parameters to your list. Don't query by ListID. If you get the error "List does not exist" after editing a DFWP or SPDataSource in SharePoint Designer, check to see that it did not add a ListID for you in the SPDataSource tag. Delete that and go by name.

Here's the name parameter:
<asp:parameter defaultvalue="Cities" name="ListName">
</asp:parameter>


Here's the WebUrl parameter.
<asp:Parameter Name="WebUrl" DefaultValue="{sitecollectionroot}"/>

** Note:  You can also use the older "/" instead of "{sitecollectionroot}", but I like the sitecollectionroot param as it is more accurate when read and the newest syntax.

The above parameters tell the SPDataSource to query the Cities list in the site collection root.


In all, here's the syntax for an SPDataSource accessing an External List named "Cities":
<sharepointwebcontrols:spdatasource datasourcemode="List" id="CitiesDataSource" runat="server" selectcommand="&lt;View&gt;&lt;ViewFields&gt;&lt;FieldRef Name='CITY'/&gt;&lt;/ViewFields&gt;&lt;/View&gt;" useinternalname="true" useserverdataformat="true"><selectparameters><br /><asp:parameter defaultvalue="Location Cities" name="ListName"><br /><asp:parameter defaultvalue="{sitecollectionroot}" name="WebUrl"><br /></asp:parameter></asp:parameter></selectparameters></sharepointwebcontrols:spdatasource>



 A few peculiarities as you craft your CAML query of an External List:


  • SPDataSources tied to External Lists really likes ViewFields specified in the CAML query.  You may get some weird execution errors until you add a few fields.  I've had CAML queries work beautifully without the ViewFields attribute of the CAML query set, only to come in the next day to a weird Correlation ID error.  When I looked up the error it said something along the lines of the query exceeding the size allowed, etc.  I think this may be when you have very wide tables, or attributes of the ECT and thus the list column tied to really long fields.  In my case, I had several nvarchar(4000) fields.
  • The RowLimit attribute of the CAML query for external lists does not seem to limit the result set coming back. I've had to resort to filtering the number of rows returned in the XSLT at times.
  • HTML Encode your CAML.  Yes, you might get away with a typed in CAML query, but it won't be as bulletproof at run time and is required for many syntax circumstances.
The next thing to do is tie a dropdown or some other data driven component to your SPDataSource, just as you would any other SPDataSource.

In this scenario, I want a dropdown tied to my CitiesDataSource SPDataSource that has a default value.  I'd also like a custom javascript method to fire when I change it.

<asp:dropdownlist appenddatabounditems="true" cssclass="ddl_city" datasourceid="CitiesDataSource" datatextfield="CITY" datavaluefield="CITY" id="ddlCity" onchange="javascript:ChangeCity(this);" runat="server"></asp:dropdownlist>
   
<asp:listitem selected="True" text="City" value="City"></asp:listitem>

So, there you have it - a dropdown tied to an external list, which is tied to an external content type that is tied to a view in a database!

8 comments:

  1. I was at the edge of my seat. ;)
    Love you. Glad there are some super smart people like you out there to figure this all out. :)

    ReplyDelete
  2. Had a question with BCS external list and SPDataSource to display data in an GridView. I posted my original question on the MSDN forum, but thought to ask you as well. Here is the link to my question http://social.msdn.microsoft.com/Forums/en-US/sharepointdevelopmentprevious/thread/c8690684-e4ff-4c8c-9f12-47624cb3ee0b

    I keep getting errors as the DataSource cant seem to find the external list. Any ideas?

    ReplyDelete
  3. Yes. I looked at your SPDataSource.

    You need to specify the list name for the "ListName" parameter, not the GUID of the list. You can use the GUID of course, but must use the parameter named "ListId". Other than that just make sure you have the appropriate weburl using the sitecollection parameter option.

    ReplyDelete
  4. I am also having trouble filtering the results. Now that my GridView is populating correctly, I need to filter my GridView when a user types text into a textbox. Do you have any thoughts on how to successfully filter-as-you-type using jQuery? Many thanks.

    ReplyDelete
  5. source the gridview from an SPDataSource that takes a parameter from the input control in the datasource.

    Then, use the autocomplete jquery widget on the input box to do asynchronous ajax refreshes of the encapsulating html element.

    http://jqueryui.com/autocomplete/#default

    ReplyDelete
  6. Specifically, you could use the "change" or "search" method of the autocomplete widget.... I'd probably also have the autocomplete options driven by a second data source that generates auto complete options, but I recognize you may not have that requirement.....

    ReplyDelete
  7. Nathan, I am sorry, but I was confused by your explanation. My textbox has an id of "txtID", my SPDataSource has an id of "dsClient", and my asp:GridView has an id of "grdClient". I also am using jQuery version 1.8.0 which, I believe, includes the autocomplete function. When I call the textbox to filter the grid using jQuery nothing happens. I have 2 columns in my asp:GridView which are "clientname" and "clientid". I am attempting to filter on the client name column. Do you have a sample you could show me, because all my attempts are not working. Thank you again.

    ReplyDelete
  8. Check out this link:

    http://forums.asp.net/t/1686150.aspx/1?Datagridview+depending+on+dropdown+selection

    Replace the SqlDataSource with your SPDataSource and get the gridview working off the parameter from the dropdown.

    The above is the core functionality you need.

    Then, configure the html select you have to get its options from another SPDataSource, like this code snippet:

    http://sharepoint.stackexchange.com/questions/22823/using-spdatasource-with-a-dropdownlist-need-to-dynamically-pass-sp-list


    Then, you can enhance the dropdown with the jquery autocomplete functionality like this:

    http://jqueryui.com/autocomplete/#combobox

    ReplyDelete