Friday, June 18, 2010

SharePoint 2010 Visio Workflows and Nintex

As you most likely know, especially if you have a SharePoint 2010 Beta subscription, SharePoint 2010 will support designing workflows in Visio 2010. You may think that this will make third-party workflow design tools, such as Nintex Workflow 2007, less important. I beg to differ.

Mike Fitzmaurice of Nintex just wrote a blog post about this, explaining the reasons why Visio 2010 Workflows is not the end of third-party add-ons.

Now, Mike is a very nice guy, I worked with him while writing the Using Nintex Workflow 2007 issue of USPJ, and after we had a particularly constructive telephone meeting, he even said ‘Thank you’ in Norwegian. That sort of thing leaves an impression. I have a few heroes in the SharePoint world, and Mike is definitely on that list.

However, in this case, he’s too shy. Here’s my perspective.

Third-party developers have been developing products for Windows for years, despite there being free Microsoft alternatives. There’s a reason why there is a plethora of software such as UltraEdit, NotePad++, etc, despite the fact that a text editor ships with Windows. There’s a reason why media players such as Media Player Classic still enjoy widespread adoption, despite Microsoft including Windows Media Player with virtually any piece of software. Browsers continue to flourish, regardless of how much Internet Explorer is now a critical update to Windows.

The reason for this rich and competing product offering is that third-party developers are simply more creative. I’m not saying Microsoft isn’t innovative or creative, but considering they only have a limited number of employees, they can’t imagine everything. Also, when Microsoft releases a product, people start learning the technology and quickly find ways to improve the experience.

A very good example of how this works is Nintex Workflow 2007. Microsoft has designed a rich and massive workflow framework, and Nintex has designed a vastly improved interface to that framework. Even when considering the apparent ease of SharePoint Designer, Nintex WF is years ahead.

Nintex simply is better at doing workflow than Microsoft. In fact, Nintex is so much better at doing workflow than Microsoft, that I perceive the Visio 2010 workflow integration to be an attempt from Microsoft to narrow the gap.

Does this mean that with the release of SharePoint 2010, that Microsoft will catch up? Not by a long shot. I’m not privy to any NDA material, but based on what I have seen from the Visio 2010 beta, Nintex could quite comfortably sit quite still and do quite nothing, and still be quite ahead.

So no, Visio 2010 workflow integration wont be the end of third-party workflow development. Quite the contrary, I suspect Nintex and other developers to put out products that will make your workflow experience a lot better than what you get out-of-the-box.

But that’s just my 2 cents.

Saturday, June 12, 2010

CRUD for SharePoint 2010 external lists using Visual Studio 2010

In our last blog of this series Walkthrough of creating a SharePoint 2010 external list using Visual Studio 2010 , we introduced how to create a simple “Hello world” external list in SharePoint 2010 using Business Data Connectivity Designer in Visual Studio 2010.

In this blog, we will show you how to pull data from an external database into an external list and enable Create, Read, Update and Delete (CRUD) functions to the external list.

If you already have a “Northwind” database, you can skip this section. Otherwise, please download SharePoint2010_BDCSamples.zip from here and extract the SQL script file CreateSampleNorthwindDB.sql.

Open Visual Studio. Go to View->Server Explorer. Right click Data Connections in Server Explorer, and select Create New SQL Server Database.
1. In the prompt dialog, type “localhost\sqlexpress” in Server Name text box, and give the new database name “SampleNorthwind”.
* If you're using the SQL Express that comes with SharePoint Server, please replace “localhost\sqlexpress" with "localhost\sharepoint”.
2. Start a Command Prompt. Go to Start->Run, type “Cmd” in the text box and click OK.
3. In the Command Prompt, type in following command and press enter:

sqlcmd -S localhost\sqlexpress -d samplenorthwind -i <Path of CreateSampleNorthwindDB.sql file>

Create BDC Project

Create a new C# BDC Model project and rename it “BdcSampleCSharp”. VB code snippets will also be provided, so you can create VB BDC Model project if you want. In this walkthrough, we will use C# project as an example. (Check this blog for how to create a BDC project)

Connect to external data source

To use the SampleNorthWind database, we add a LINQ to SQL model to the project:
1. On the Project menu, click Add New Item, in the prompt Add New Item dialog select Data in the Installed Templates pane, in the Templates pane select LINQ to SQL Classes, in the Name box, type “Customer”, and then click Add.

2. In the Server Explorer, go to Data Connections->[hostname]\sqlexpress.SampleNorthWind.dbo->Tables->Customers, drag the Customers table and drop it on the Customer.dbml design surface.
3. Add a new class file and rename it “CustomerDataContext.cs”. Replace the code of the class with the following code snippet.
Note: We made the connection string a constant in the code only for demo purpose, if you’re using your own database, modify the connection string as needed. In our future post we will introduce how to set the connection string in a custom property on LobSystemInstance inside the BDC model and read the value through IContextProperty interface at runtime.

C#:



public partial class CustomerDataContext

{


private const string ConnectionString = @"Data Source=localhost\SQLEXPRESS;Initial Catalog=SampleNorthwind;Integrated Security=True;Pooling=False";


public CustomerDataContext() :


base(ConnectionString, mappingSource)


{


OnCreated();


}


}

VB:


Partial Public Class CustomerDataContext

Private Const ConnectionString As String = "Data Source=localhost\SQLEXPRESS;Initial Catalog=SampleNorthwind;Integrated Security=True;Pooling=False"


Public Sub New()


MyBase.New(ConnectionString, mappingSource)


OnCreated()

End Sub


End Class
 
 
Design BDC Model



1. On the design surface, delete entity Entity1 which is created by default. On the View menu, click on Toolbox if it is not shown. Create a new entity by drag and drop the Entity icon from Toolbox onto design surface (see the screenshot below). In the Properties Browser, change the value of Entity’s Name property to “Customer”.


2. Create a new Identifier CustomerID on entity Customer. To do so, on the design surface, right click the entity, click Add->Identifier. A new identifier appears on the entity, and then rename it to “CustomerID”.


3. Create a Specific Finder method for the entity. To do so, on the design surface, select entity Customer, you could find a <Add a Method> command in the Method Details Window. If the Method Details Window is not opened, you can find it in menu View->Other Windows->BDC Method Details. From the <Add a Method> drop-down list, select Create Specific Finder Method:




A method named ReadItem appears on entity Customer. In the Method Details Window, you will find that the method takes a In parameter and a Return parameter. In the next step we will define TypeDescriptors associated with these parameters.

4. Add TypeDescriptors for the return parameter Customer. The edit need to be done in BDC Explorer. You can find it by going to View->Other Windows->BDC Explorer.


a) In the Method Details Window, click <Edit> command in the drop down control from TypeDescriptor Customer as depicted below. After the click the BDC Explorer will get focused on the TypeDescriptor Customer.

 
b) In BDC Explorer right click the focused TypeDescriptor Customer and select Add Type Descriptor:
 
 

A new TypeDescriptor is created under TypeDescriptor Customer. In the Properties Browser, rename it to “CustomerID”. Next you need to set the Identifier property to “CustomerID” to tell the BCS runtime which identifier this TypeDescriptor represents. Here is a screenshot of Properties Browser after this step:



c) Continue to add following TypeDescriptors under Customer by repeating the operation described above: Address, City, CompanyName, ContactName, Country, Fax, Phone, PostalCode and Region, for each TypeDescriptor, you need to change its Type Name to make sure they match the type defined in the LINQ to SQL model. In this example, all the TypeDesriptors have a type of System.String which is the default one so we do not need to change them. After this step, we get the following TypeDescriptors in BDC Explorer:


d) Next we need to define the actual type of the TypeDescriptor Customer. Click TypeDescriptor Customer in BDC Explorer. You will find in the Properties Browser the value of Type Name property is System.String by default, we need to change it to “BdcSampleCSharp.Customer, BdcModel1” which is a LobSystem qualified type name. This specifies the actual the data type of the data structure that is represented by this TypeDescriptor Customer.


5. Create the other types of methods by the same way described in step 6. After this step we will have five methods in the entity Customer: ReadItem, ReadList, Create, Update and Delete. If you check the ReadList method in Method Details Window, the TypeDescriptors of the return parameter have already been defined with the same structure as we just built above. This is because when creating a new method, BDC designer will search the possible TypeDescriptors defined in the other methods of this entity and copy them to the newly created methods. This saves the developers so much time to define them repetitively.

Add code behind to access external data source
Now it’s time to add code to implement the CRUD functions. In Solution Explorer, find and open CustomerService.cs, and then replace the implementation with the following code snippet:

C#:



public static Customer ReadItem(string customerID)


{


CustomerDataContext context = new CustomerDataContext();

Customer cust = context.Customers.Single(c => c.CustomerID == customerID);

return cust;

}


public static IEnumerable<Customer> ReadList()


{

CustomerDataContext context = new CustomerDataContext();

IEnumerable<Customer> custList = context.Customers;

return custList;

}

 
public static Customer Create(Customer newCustomer)


{

CustomerDataContext context = new CustomerDataContext();


context.Customers.InsertOnSubmit(newCustomer); context.SubmitChanges();


Customer cust = context.Customers.Single(c => c.CustomerID == newCustomer.CustomerID);

return cust;

}


public static void Delete(string customerID)


{

CustomerDataContext context = new CustomerDataContext();

Customer cust = context.Customers.Single(c => c.CustomerID == customerID);

context.Customers.DeleteOnSubmit(cust);

context.SubmitChanges();

}


public static void Update(Customer customer, string customerID)


{

CustomerDataContext context = new CustomerDataContext();


Customer cust = context.Customers.Single(c => c.CustomerID == customer.CustomerID);

cust.CustomerID = customer.CustomerID;

cust.Address = customer.Address;

cust.City = customer.City;

cust.CompanyName = customer.CompanyName;

cust.ContactName = customer.ContactName;

cust.ContactTitle = customer.ContactTitle;

cust.Country = customer.Country;

cust.Fax = customer.Fax;

cust.Phone = customer.Phone;

cust.PostalCode =customer.PostalCode;

cust.Region = customer.Region;

context.SubmitChanges();

}


VB:

 
Public Shared Function ReadItem(ByVal customerID As String) As Customer

Dim context As New CustomerDataContext

Dim cust = (From c In context.Customers _

Where c.CustomerID = customerID _

Select c).Single()

Return cust

End Function

 
Public Shared Function ReadList() As IEnumerable(Of Customer)

Dim context As New CustomerDataContext

Return context.Customers

End Function


Public Shared Function Create(ByVal newCustomer As Customer) As Customer

Dim context As New CustomerDataContext

context.Customers.InsertOnSubmit(newCustomer)

context.SubmitChanges()

Dim cust = (From c In context.Customers _

Where c.CustomerID = newCustomer.CustomerID _

Select c).Single()

Return cust


End Function


Public Shared Sub Delete(ByVal customerID As String)

Dim context As New CustomerDataContext

Dim cust = (From c In context.Customers _

Where c.CustomerID = customerID _

Select c).Single()

context.Customers.DeleteOnSubmit(cust)

context.SubmitChanges()

End Sub


Public Shared Sub Update(ByVal customer As Customer, ByVal customerID As String)

Dim context As New CustomerDataContext

Dim cust = (From c In context.Customers _

Where c.CustomerID = customer.CustomerID _

Select c).Single()

cust.Address = customer.Address

cust.City = customer.City

cust.CompanyName = customer.CompanyName

cust.ContactName = customer.ContactName

cust.ContactTitle = customer.ContactTitle

cust.Country = customer.Country

cust.Fax = customer.Fax

cust.Phone = customer.Phone

cust.PostalCode = customer.PostalCode

cust.Region = customer.Region

context.SubmitChanges()

End Sub
 
Create an external list to test out the BDC Model!
Now we are done! Let’s deploy the solution and create an external list to test it out (Check this blog to see how to create an external list). After the list is created, you will see the following page appears when you click on the list name:




 
 
 
 
 
 
 
 
 
 
 
 
All the data are pulled out to SharePoint successfully, now you can create, delete or edit customer records.
To create a new Customer, find the List Tools->Items->New Item button on the ribbon shown in the following screenshot:

 
 
 
 
 
 
 
 
 
Click on the button, New Item dialog will pop out as below:
 
 

Fill in the dialog and click Save, you will find the item created in the list right away.

To edit or delete the item, just click the down arrow at the right side of CustomerID, all the operations you need are there.




Professional SharePoint 2010 Development (Wrox Programmer to Programmer)

Monday, May 24, 2010

Customizing Ribbon Positioning in SharePoint 2010 Master Pages

As you’ll likely notice in SharePoint 2010, the Ribbon is specially positioned to always be visible on the screen, even if you scroll down the page contents. This is accomplished by placing the Ribbon into its own container and then using client-side script to make the rest of the page contents take up the remainder of the browser’s height. You can see this in action by switching between the “Browse” tab and the “Page” tab on the SharePoint default homepage: The top of the scrollbar on the right side of the browser window will move up and down depending on whether the Ribbon is opened or not.
One of the questions we hear most often is how to enable or disable this behavior on a custom master page. To answer this question, I need to explain more about how the system works. There are three parts of the system: The markup on the master page, the CSS styles in the stylesheet, and the ECMAScript code linked to on the page.

Master Page Markup


Let’s start with the markup. SharePoint 2010 compatible master pages require many components, but there are a few key components that are used by the Ribbon positioning system. Going from top to bottom, the first component of note is the “_fV4UI” ECMAScript variable. In v4.master, it looks like this:

<script type=”text/javascript”>

var _fV4UI = true;

</script>

This block tells the rest of the ECMAScript code in SharePoint that this master page is operating in SharePoint version 4 mode (version 4 corresponds to SharePoint 2010). This variable is used in the script that handles Ribbon positioning which I’ll discuss later on.
Moving down the master page source code, the next interesting component is the “body” tag. In v4.master, it looks like this:
<body scroll=”no” onload=”...” class=”v4master”>

The two important parts of the tag are the “scroll” and “class” attributes. The “scroll” attribute is used to force IE to hide the page scrollbar. Since SharePoint handles the scrollbar independently, we need to stop the browser from interfering. For other browsers, the scrollbar is hidden using CSS styles which are explained later in this post. The CSS class applied to the “body” tag is used to apply CSS styles in the corev4.css stylesheet which are part of the Ribbon positioning system. If you are using a custom stylesheet, you may leave this out; but make sure to apply the needed CSS styles in some other way (perhaps by referencing “body” directly in your stylesheet).

The next important component on the master page is the Ribbon container. In v4.master, it looks like this:


<div id=”s4-ribbonrow” class=”s4-pr s4-ribbonrowhidetitle”>
</div>


Let’s look at this piece by piece. First, you’ll notice that the element is a div so that it takes up the width of the browser and acts as a block-level HTML element. After that, is the ID which is a mandatory part of the system: the ECMAScript logic for Ribbon positioning uses this ID to find the Ribbon container. If you omit or change this ID, the Ribbon positioning system will abort and your Ribbon will not stay docked to the top of the page. I’ll discuss the steps to enable and disable the system later on in this post. The next part of the Ribbon container element is the “class” attribute which lists the CSS classes applied to it. The first one is just a layout class used to make the container full width and block displayed. “pr” stands for Page Row. The second CSS class is used in the Ribbon positioning system to tell what state the Ribbon is in currently. It is set to “s4-ribbonrowhidetitle” by default, but that is changed to the correct value once the ECMAScript code on the page initializes.

The Ribbon control itself lives inside the Ribbon container. Also present are the controls that appear in the top row of the ribbon (e.g. Site Actions, breadcrumb navigation button, edit/save button, the Personal Actions menu, etc.), the notifications area, Publishing Console, and Web Part adder. Nothing else should be placed into this container as its height is set using static values that will not adjust to additional content. To add more chrome above the Ribbon, you should add a new element above the Ribbon container.

Immediately following the Ribbon container are two other important elements on the master page: the Workspace element and the Body Container. These elements look like this in v4.master:

<div id=”s4-workspace”>

<div id=”s4-bodyContainer”>
</div>

</div>
 
The Workspace container is the element that remains scrollable when the Ribbon positioning system is enabled. Like the Ribbon container, its ID is mandatory as it must be referenced from ECMAScript code during page load. Directly inside of the Workspace container is the Body Container. Its ID is also required. Body Container is used to determine the width of the page content within the client-side script. Both of these elements must be present on the master page for the Ribbon positioning system to run. If one or both are missing, the system will abort.



There is one more interesting element on the master page: The Title container. As you’ll notice in v4.master, opening a Ribbon tab other than Browse will hide the page title and top navigation and replace it with the appropriate Ribbon tab. The important distinction to note here is that the “Browse tab” is not actually a tab. In reality, it is simply an empty Ribbon tab with normal HTML below it. In v4.master, the Title container looks like this:

<div id=”s4-titlerow” class=”s4-pr s4-notdlg s4-titlerowhidetitle”>
</div>

Like the other containers discussed above, the Title container must have a specific ID, in this case “s4-titlerow.” However, unlike the other containers, if the Title row ID is not present, the Ribbon positioning system will still run. If you leave out the ID or remove the element completely, the system will just ignore it and handle everything else appropriately. This means that if you leave the element but remove the ID, the Title area will not be removed from the page when you open a Ribbon tab. The CSS classes are also worth noting: As I explained above, “s4-pr” just makes the element take up the full browser width and display as a block element; “s4-notdlg” is used to stop the element from appearing if the page is being loaded in a Modal Dialog; “s4-titlerowhidetitle” is used by the Ribbon positioning ECMAScript to handle the current state of the Ribbon - just like the corresponding class on the Ribbon container, it is updated accordingly when the client-side script initializes at page load.
 
CSS Styles
 
All of the CSS styles pertinent to the Ribbon positioning system are in corev4.css. They are all required for the Ribbon positioning system to function properly. The first style rule of importance is “body.v4master” which is defined as follows:
body.v4master {

height: 100%;

width: 100%;

overflow: hidden;

}


This makes the body of the page take up the full width and height of the browser and hides the scrollbar for most browsers (remember that the “scroll” attribute on “body” handles this for some versions of IE).
Next is the CSS rule for the Ribbon container:


body #s4-ribbonrow {

min-height: 43px;

background-color: #21374c;

overflow-y: hidden;

}

This makes the Ribbon row take up 43px of vertical space at minimum and allows groups that cannot be fit into the browser (after scaling) to “fall off the edge” of the ribbon.
Below the Ribbon container styles are a set of styles that only apply to printing. I won’t go through each of these, but suffice to say that these styles are meant to undo some of the positioning applied by the system so that the full page displays when printed.
Further down the stylesheet are the styles for the Workspace and Body Container elements:
body s4-workspace {

overflow-y: scroll;

overflow-x: auto;

position: relative;

left: 0px;

}

body #s4-bodyContainer {

min-width: 760px;

}

First, we make the Workspace element always show a vertical scrollbar (to prevent shifting of page contents on load) and show a horizontal scrollbar only if necessary. The other two declarations in this rule are used for other layout purposes. The Body Container gets assigned a minimum width to ensure that shrinking the browser window down won’t render SharePoint unusable.

ECMAScript


The real logic of the Ribbon positioning system is in the ECMAScript code. I won’t go through the actual code that runs, but if you are curious, you can open up init.debug.js in your layouts\1033 folder and look for “FixRibbonAndWorkspaceDimensions()”. The Ribbon positioning logic is triggered in three ways: when the page loads, when the browser is resized, and when the Ribbon is minimized or maximized (for example, by double-clicking a tab title). Note that switching between the “Browse” tab and other tabs is a form of minimizing and maximizing the Ribbon.
The logic generally works like this:


· First, we look for the four interesting elements on the page: the Ribbon container, the Workspace container, the Body Container, and the Title container. If any of the first three cannot be found, the code aborts.


· We then check if the Workspace element has the CSS class “s4-nosetwidth” applied to it. If so, it will not set the width of any elements. This is useful if you have a fixed-width master page design.

· Next, we use static values plus some runtime information to determine the height that should be set on the Ribbon container. If the Ribbon container has its “visibility” style set to “hidden,” the system will set the Ribbon container’s height to 0px. This is the supported way to hide the Ribbon container for certain users but keep the Ribbon positioning logic when the Ribbon is displayed.


· Now, the important calculation occurs: The system gets the browser’s viewport height (the area of the window that contains the SharePoint page) and subtracts the height of the Ribbon container and the top position of the Ribbon container to determine how much space is left below for the Workspace container.


· The calculated height is set to the Workspace element and then some extra logic is run to set width and scroll position information accordingly.


At the very end of the ECMAScript logic, a set of callback functions are run for components that need to know when the Ribbon positioning system has run. If you need to run some code at this point, you can call SP.UI.Workspace.add_resized(handler) which will add your handler to the list.

Enabling and Disabling the System


Now that you have a better idea of how everything works, let’s run through the necessary steps to enable and disable the system for your master page.


Enabling the Ribbon Positioning System

The easiest way to take advantage of the Ribbon positioning system is to derive your custom master page from v4.master. All you have to do is pay attention to the components listed above so that you don’t remove something that’s important. If you end up using a custom CSS file, make sure to include all of the CSS rules shown above. The ECMAScript is included automatically by the ScriptLink control. ScriptLink is required on all SharePoint 2010 compatible master pages, so you get that for free.
If you are retrofitting an old master page to work with the Ribbon positioning system, you can simply follow the steps at Upgrading an Existing Master Page to the SharePoint Foundation Master Page to upgrade your master page. Most of what is discussed on this post is covered briefly in that documentation.
Disabling the Ribbon Positioning System

If you’re customizing v4.master, but wish to let the Ribbon scroll up with the page contents, you’ll need to do a few things:




In your master page:



1) Remove the “_fV4UI” variable from the top of the page. It will still be emitted by the SPWebPartManager later on in the page’s markup, but it will not be present when the on-load events for the Ribbon position system are usually attached. This will stop the code from running when the page loads which will improve rendering performance.



2) Remove or change the Workspace element’s ID to make the ECMAScript code abort early during window resizes and Ribbon minimize/maximize events. You can leave the element, but just change or remove the ID.
3) Remove the “scroll” attribute from the Body tag.
In your CSS stylesheet:
1) Remove or change the width, height, and overflow declarations on the “body” tag.
2) You can optionally remove the s4-workspace and s4-bodyContainer rules since they are no longer necessary.





Pro SharePoint 2010 Solution Development: Combining .NET, SharePoint, and Office

Saturday, May 22, 2010

Creating a SharePoint 2010 external list using Visual Studio 2010

Today I want to introduce one of them, Business Data Connectivity (BDC) designer, which is available in the project template Business Data Connectivity Model. If BDC is new to you, here is a short description. BDC is one of two most important architectural components of Microsoft Business Connectivity Services (BCS) which enables users to read and write data from external systems—through Web and Windows Communication Foundation (WCF) services, databases, and Microsoft .NET Framework assemblies—from within Microsoft SharePoint 2010 and Microsoft Office 2010 applications. This MSDN webpage Business Data Connectivity (BDC) Service has a more descriptive version.

Visual Studio 2010 helps a SharePoint developer to develop, debug and deploy .NET assemblies as external data sources to SharePoint. In the following paragraphs, I will walkthrough with you how to create your first SharePoint external list using Visual Studio 2010.

1. Create a new BDC Model project. (Main menu: File -> New -> Project…). In the left column of the New Project dialog, you are able to find node 2010 under tree view Visual C# -> SharePoint. Similarly you can find same node under Visual Basic -> SharePoint. In the middle column of the dialog, you should be able to see Business Data Connectivity Model listed as one of the project templates. See the screenshot as follows. Here I am creating BDC Model project in Visual C#. You are able to do the same things in Visual Basic.


 
2. After clicking [OK] button in the New Project dialog, the SharePoint Customization Wizard dialog will be displayed. In this dialog you can customize the local site you want to target and trust level for the SharePoint solution. Since a BDC model is deployed to a farm, a collection of one or more SharePoint servers and one or more SQL servers, only “Deploy as a farm solution” option is enabled. Here is the screenshot of the dialog.




3. When you click [Finish] button in the SharePoint Customization Wizard dialog, the BDC Model project will be created. There are four main UI panes that help you manage the BDC model visually. They are the BDC Designer Surface, BDC Method Details Pane, BDC Explorer, and Properties Browser.


  • The BDC Designer Surface allows editing entities, identifiers, methods, and associations between entities. And you can do that either from toolbox or context menus.
  • The BDC Method Details pane, where its name is already self-explanatory, lets you edit everything related to a method, from the method itself, its parameters to its parameters’ type descriptors, from method instances to filter descriptors, etc.
  • BDC Explorer lists and organizes metadata objects in the BDC model in a tree view. It lets you to browse and search metadata objects in a graphical way and allows you to copy/cut/paste type descriptors between different parameters or type descriptors.
  • Properties Browser gives you a familiar way of editing components and attributes of BDC Models. We use it to supplement the functions offered by the other three panes and list all the attributes for a particular metadata object for editing. Here is a typical layout of a BDC Model project as described above.



4. If you notice, there is already a default entity generated for you when the project is created. This default entity also has an identifier and two methods ReadItem and ReadList created. One is a Finder method that is to return a collection of data. The other is a Specific Finder method that is to return a specific entry based on the input parameter(s).



5. Now let’s deploy to the SharePoint server. You can either click the deploy menu item in main menu (Build -> Deploy Solution), or in the context menu of the project or the solution. In the output you will see several activities happening including packaging, solution retraction/addition, deployment, etc.


6. Let’s open the target site and see if our model has been successfully deployed. Open the target site with any browser supported by SharePoint, like Internet Explorer 8. Create an external list based on the model we just deployed. Here are the steps to create an external list in case you are new to it.

  • In main menu: Click Site Actions -> More Options…
  • On the Create dialog, select External List, click [Create] button in the right column
  • On the next external list create form, type a name for the list. Check ‘Yes’ to display the list on the Quick Launch for easy access. Then click to select the model we just deployed in the External Content Type Picker form. Click [Create] on the external list create form. Now the Hello World list is displayed.

 

 In the main menu under List Tools -> Items, you may find only “View Item” option is enabled. Guess why? Yes, it is because the default entity only has a Finder method to view the entire list and a Specific Finder method to view a specific item.





SharePoint 2010 User&rsquo;s Guide: Learning Microsoft&rsquo;s Business Collaboration Platform (Expert's Voice in Sharepoint)

Saturday, April 24, 2010

Using Move-SPSite to move Site Collections in SharePoint 2010

One of most popular SharePoint 2007 blog posts is about using the STSADM operation mergecontentdbs to move site collections from one content database to another. It showed up in Service Pack 1 of SharePoint 2007 and made the process of shuffling site collections around much easier. Through service packs and cumulative updates it got stronger over the years.

When SharePoint 2010 hit the streets our options got even better. Not only do we get mergecontentdb's direct replacement, Move-SPSite, but we also get a way to remove some of the need for its use in the first place. In SharePoint 2007 there was no way out of the box to create a new site collection in a specific content database that already existed.

The closest we had was the createsiteinnewdb STSADM operation that would create a new content database for our new site collection. To create a new site collection in a specific content database we had to use tricks like put the content database in the offline mode, or play with its maximum site number. These techniques worked okay if you had a small SharePoint 2007 shop, but once a couple new administrators got into the mix things got ugly. Fortunately for us, SharePoint 2010 is here to save us from all of that. I was talking to my buddy Anders Rask about this last week. He was doing this for a SharePoint 2007 environment and it reminded me how much better the story is in SharePoint 2010. Anders' pain turned into a tasty new blog post for you guys. Everyone thank Anders.



We no longer need mergecontentdbs, we now have the Move-SPSite PowerShell cmdlet. It's a little easier to use than its STSADM counterpart, and because it's in PowerShell, it's a lot more flexible. It can also dance better and it cooks a mean omelet. At its most simple, you can use it like this:


Move-SPSite http://sharepoint/sites/moveme -DestinationDatabase WSS_Content2

For this to work, the content database that the moveme site collection is currently in must be on the same SQL server as WSS_Content2 and moveme but be in the same web application as the WSS_Content2 database. Mergecontentdbs has those same limitations though, so we're used to them.

Instead of having to cobble together XML files to do multiple site collections at a time, like we did with mergecontentdbs, now we can just loop through a collection of site collection objects and only move only the ones that meet our specific criteria. For instance, to move all the site collections where contoso\todd is the owner, to the content database WSS_Content2 we would use this line:


Get-SPSiteAdministration
Where-Object { $_.OwnerLoginName -eq "contoso\todd" }
Move-SPSite -DestinationDatabase WSS_Content2


The first cmdlet we use is Get-SPSiteAdministration. This is like Get-SPSite, gets us a collection of the site collections in our farm. Unlike Get-SPSite, Get-SPSiteAdministration includes site collections that the user running the command does not have access to. We can use any site collection property as our filter, including name, template, content database, anything we would like. Our imagination is the limit.


Like I mentioned earlier, part of the reason we needed mergecontentdbs was because there was no good way to control which existing content database a new site collection went into in SharePoint 2007. We had a few workarounds, but like most workarounds they had some downsides. For instance, if we set a content database to Stopped or Offline a new site collection would not go there. First, you had to somehow decipher the cryptic meanings of "offline" and "stopped. Not even the Rosetta Stone is any help there. Setting a database in that mode had some collateral damage, like profile sync no longer worked. Our second technique was to set every content database's maximum sites setting to its current number of sites. That way SharePoint had to put the new site collection someplace else. The problem with that is that when you're culling out old site collections your maximum is no longer the current number of site collections. After each site collection deletion you have to go through your databases and adjust the maximum to the new current number of sites. That is kind of a pain, and tough to remember to do. Both of these techniques really start to fail once you have more than one person managing your farm. Again, SharePoint 2010 and its sidekick PowerShell come to the rescue.


In SharePoint 2010 when we create a new site collection with New-SPSite we have an optional -ContentDatabase parameter we can define. If we want that new site collection to go into a specific content database, we just tell New-SPSite and it puts it there for us. How considerate. However, there was one tradeoff. There is no PowerShell equivalent of the old Createsiteinnewdb STSADM operation. The content database has to exist for it to work with New-SPSite. That's okay though; creating new content databases with New-SPContentDatabase is fun, so we don't mind doing it anyway. Just create the database first and then drop your new site collection into it.




Professional SharePoint 2010 Administration

Wednesday, April 7, 2010

SQL-Server-Functions

Functions let you replace and access strings and dates in SQL Server. You can call them passing times, tables, dates, and other variables, and get back results. Here are some articles to help you get started fast with functions, and help you learn how to pick the right function in SQL Server 2005 and 2008:

Common SQL Server Functions

These functions are the bread and butter of your stored procedures and views:

SQL Server Replace Function

To replace characters in a string, use SQL Server 2005's built-in string functions:

SQL Server String Functions

SQL Server can do more than just replace characters in a string: it can also uppercase and lowercase strings, calculate string lengths, and more.

SQLServerPedia Editors' Favorite Articles on Functions

Our editors recommend these links:

Monday, March 22, 2010

SharePoint 2007 - Adding Event Receivers using STSADM

Recently I was thinking about what might be the best way to allow people to script the settings that enable the storing of page history.  I decided that I would start off by providing a simple STSADM command that would allow you to script the adding of event receivers to a list (fortunately making it work for webs and content types didn't really require any additional work so I covered those as well).  The new command I created is called gl-addeventreceiver.


I'll probably eventually come up with something else but this at least is a start - I primarily don't like it because it requires that people know how the web part history project works so I'll probably create a command specific to enabling the history so that it abstracts out the technical details which could always change (there just hasn't been a whole lot of interest in the project so I've not really spent much time on it).  In the meantime this command is a nice generic tool that could be used by developers and administrators for various reasons (I'll probably create an enum and remove command to supplement this one - just haven't gotten around to it yet).


Adding an event receiver via code is really very simple - just a matter of calling the Add method of the SPEventReceiverDefinitionCollection object which you can get via the EventReceivers property of either the SPList, SPContentType, or SPWeb object.  To get an existing event receiver (to check for existence as we don't want to add twice) we have to loop through the collection comparing the assembly name, class name, and event receiver type.

1: /// <summary>
   2: /// Adds an event receiver to the specified target
   3: /// </summary>
   4: /// <param name="url">The URL.</param>
   5: /// <param name="contentTypeName">Name of the content type.</param>
   6: /// <param name="target">The target.</param>
   7: /// <param name="assembly">The assembly.</param>
   8: /// <param name="className">Name of the class.</param>
   9: /// <param name="type">The type.</param>
  10: /// <param name="sequence">The sequence.</param>
  11: /// <param name="name">The name.</param>
  12: public static void Add(string url, string contentTypeName, TargetEnum target, string assembly, string className, SPEventReceiverType type, int sequence, string name)
  13: {
  14:     using (SPSite site = new SPSite(url))
  15:     using (SPWeb web = site.OpenWeb())
  16:     {
  17:         SPEventReceiverDefinitionCollection eventReceivers;
  18:         if (target == TargetEnum.List)
  19:         {
  20:             SPList list = Utilities.GetListFromViewUrl(web, url);
  21:  
  22:             if (list == null)
  23:             {
  24:                 throw new Exception("List not found.");
  25:             }
  26:             eventReceivers = list.EventReceivers;
  27:         }
  28:         else if (target == TargetEnum.Site)
  29:             eventReceivers = web.EventReceivers;
  30:         else
  31:         {
  32:             SPContentType contentType = null;
  33:             try
  34:             {
  35:                 contentType = web.AvailableContentTypes[contentTypeName];
  36:             }
  37:             catch (ArgumentException)
  38:             {
  39:             }
  40:             if (contentType == null)
  41:                 throw new SPSyntaxException("The specified content type could not be found.");
  42:  
  43:             eventReceivers = contentType.EventReceivers;
  44:         }
  45:         SPEventReceiverDefinition def = Add(eventReceivers, type, assembly, className, name);
  46:         if (sequence >= 0)
  47:         {
  48:             def.SequenceNumber = sequence;
  49:             def.Update();
  50:         }
  51:     }
  52: }
  53:  
  54: /// <summary>
  55: /// Adds an event receiver to a the specified event receiver definition collection.
  56: /// </summary>
  57: /// <param name="eventReceivers">The event receivers.</param>
  58: /// <param name="eventReceiverType">Type of the event receiver.</param>
  59: /// <param name="assembly">The assembly.</param>
  60: /// <param name="className">Name of the class.</param>
  61: /// <param name="name">The name.</param>
  62: /// <returns></returns>
  63: private static SPEventReceiverDefinition Add(SPEventReceiverDefinitionCollection eventReceivers, SPEventReceiverType eventReceiverType, string assembly, string className, string name)
  64: {
  65:     if (GetEventReceiver(eventReceivers, eventReceiverType, assembly, className) == null)
  66:     {
  67:         eventReceivers.Add(eventReceiverType, assembly, className);
  68:         SPEventReceiverDefinition def = GetEventReceiver(eventReceivers, eventReceiverType, assembly, className);
  69:         if (!string.IsNullOrEmpty(name))
  70:         {
  71:             def.Name = name;
  72:             def.Update();
  73:         }
  74:         return def;
  75:     }
  76:     return null;
  77: }
  78:  
  79: /// <summary>
  80: /// Gets the event receiver.
  81: /// </summary>
  82: /// <param name="eventReceivers">The event receivers.</param>
  83: /// <param name="eventReceiverType">Type of the event receiver.</param>
  84: /// <param name="assembly">The assembly.</param>
  85: /// <param name="className">Name of the class.</param>
  86: /// <returns></returns>
  87: private static SPEventReceiverDefinition GetEventReceiver(SPEventReceiverDefinitionCollection eventReceivers, SPEventReceiverType eventReceiverType, string assembly, string className)
  88: {
  89:     foreach (SPEventReceiverDefinition erd in eventReceivers)
  90:     {
  91:         if (erd.Assembly == assembly && erd.Class == className && erd.Type == eventReceiverType)
  92:         {
  93:             return erd;
  94:         }
  95:     }
  96:     return null;
  97: }


The help for the command is shown below:


C:\>stsadm -help gl-addeventreceiver

stsadm -o gl-addeventreceiver


Adds an event receiver to a list, web, or content type.

Parameters:
        -url <web or list URL>
        -assembly <assembly>
        -class <class name>
        -type <itemadding | itemupdating | itemdeleting | itemcheckingin | itemcheckingout | itemuncheckingout | itemattachmentadding | itemattachmentdeleting | itemfilemoving | fieldadding | fieldupdating | fielddeleting | sitedeleting | webdeleting | webmoving | itemadded | itemupdated | itemdeleted | itemcheckedin | itemcheckedout | itemuncheckedout | itemattachmentadded | itemattachmentdeleted | itemfilemoved | itemfileconverted | fieldadded | fieldupdated | fielddeleted | sitedeleted | webdeleted | webmoved | emailreceived | contextevent | invalidreceiver>
        -target <site | list | contenttype>
        [-contenttype <content type name if target is ContentType>]
        [-sequence <sequence number>]
        [-name <the name to give to the event receiver>]


The following table summarizes the command and its various parameters:



Command NameAvailabilityBuild Date
gl-addeventreceiverWSS v3, MOSS 2007Released: 9/13/2008





Parameter NameShort FormRequiredDescriptionExample Usage
url YesThe URL to the web or list to add the event receiver to.-url http://portal/pages
assemblyaYesThe fully qualified assembly name containing the event receiver class to add.-assembly "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08"

-a "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08"
classcYesThe fully qualified class name of the event receiver to add.-class Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver

-c Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver
type YesThe event type to add.  The command does not validate that you are adding the correct type for the specified target or that the specified class contains handlers for the type specified.-type itemupdated
target NoThe target type to add the event receiver to.  Must be either "list", "site", or "contenttype".  If omitted defaults to "list".-target list
contenttypectNo, unless target is contenttypeThe name of the content type to add the event receiver to if the target is contenttype.-contenttype "Page"

-ct "Page"
sequenceseqNoThe sequence number specifies the order of execution of the event receiver.-sequence 1000

-seq 1000
namenNoThe name to give to the event receiver.  The name has no significance but can be useful when later listing the event receivers.-name "Handle Saving of Page History"

-n "Handle Saving of Page History"



The following is an example of how to add three event receivers to a pages library - the three commands illustrated below constitute the required event receivers that must be enabled to turn on the web part page history:

stsadm -o gl-addeventreceiver -url http://portal/pages -assembly "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08" -class "Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver" -target list -sequence 1000 -type itemcheckedin

stsadm -o gl-addeventreceiver -url http://portal/pages -assembly "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08" -class "Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver" -target list -sequence 1000 -type itemupdating

stsadm -o gl-addeventreceiver -url http://portal/pages -assembly "Lapointe.WebPartPageHistory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3216c23aba16db08" -class "Lapointe.WebPartPageHistory.ListEventReceivers.SourceListEventReceiver" -target list -sequence 1000 -type itemupdated