Mar
17
2009

SharePoint MVPs having fun playing paintball

Thanks to all people who organized it this year, the SharePoint MVPs were invited for the traditional paintball at the MVP Summit. This year we were playing a different field from last year. We were at Ft. Lewis. Because I was not playing, I had more than enough time for some picture. The rest of my pictures can be found here.

The last day of the Summit we were part of the Microsoft “I’m a PC” campaign. In this blogpost by Mike Nash, you can find some more info on the summit and the video that we participated in. Lots of Dutch MVPs can be found in the audience :-) .

2009_0304MVPSummit20090134

2009_0304MVPSummit20090064

2009_0304MVPSummit20090066

2009_0304MVPSummit20090073

2009_0304MVPSummit20090080

2009_0304MVPSummit20090093

2009_0304MVPSummit20090120

2009_0304MVPSummit20090105

Permanent link to this article: http://www.tonstegeman.com/blog/2009/03/sharepoint-mvps-having-fun-playing-paintball/

Mar
10
2009

Logging to the Workflow History List in SharePoint workflows

I keep forgetting how to do this, so I decided to write it down. In SharePoint workflows created with Visual Studio you can easily write to the history list. This makes it easier to troubleshoot your workflows.

Helper methods

First I create 2 static helper methods that do the work for me.

public static class Common
{
    private static string SUCCESS = "Success";
    private static string FAIL = "Failed";

    public static void WriteSuccessToHistoryLog(SPWeb web, Guid workflow, string description)
    {
        TimeSpan ts = new TimeSpan();
        SPWorkflow.CreateHistoryEvent(web, workflow, 0, web.CurrentUser, ts, SUCCESS, description, string.Empty);
    }

    public static void WriteFailToHistoryLog(SPWeb web, Guid workflow, string description)
    {
        TimeSpan ts = new TimeSpan();
        SPWorkflow.CreateHistoryEvent(web, workflow, 0, web.CurrentUser, ts, FAIL, description, string.Empty);
    }
}

Logging from the workflow itself

From the code-behind of the workflow, it is easy to call these methods. We first need to get the workflow activation properties, that have a reference to the context objects:

public Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties _WorkflowProperties = new Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties();

Using the properties object, we can call our log methods:

Common.WriteSuccessToHistoryLog(_WorkflowProperties.Web, WorkflowInstanceId, "Hello!");

Logging from a custom activity

When used from a custom activity, we need to do a bit more work to pass the context of the workflow to the activity. My custom activity has custom properties for the site in which the workflow runs and the ID of the workflow instance. I pass the WorkflowInstanceId specifically to the activity, because my activities and workflows are in separate assemblies. Here are the property definitions:

public static DependencyProperty InstanceIdProperty = DependencyProperty.Register("InstanceId", typeof(Guid), typeof(AddWeb));

[DescriptionAttribute("Workflow Instance Id")]
[CategoryAttribute("Workflow Properties")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
public Guid InstanceId
{
    get { return ((Guid)(base.GetValue(AddWeb.InstanceIdProperty))); }
    set { base.SetValue(AddWeb.InstanceIdProperty, value); }
}

public static DependencyProperty ListContextWebProperty = DependencyProperty.Register("ListContextWeb", typeof(SPWeb), typeof(AddWeb));

[DescriptionAttribute("ListContextWeb")]
[CategoryAttribute("Workflow Properties")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
public SPWeb ListContextWeb
{
    get { return ((SPWeb)(base.GetValue(AddWeb.ListContextWebProperty))); }
    set { base.SetValue(AddWeb.ListContextWebProperty, value); }
}

In this sample, AddWeb is the name of my custom activity.

Calling the logging methods is then done like this:

Common.WriteSuccessToHistoryLog(ListContextWeb, InstanceId, "Hello from an activity.");

The last thing to do is pass the correct parameters to pass the correct parameters to your activity. Open the workflow in designer mode, select your activity and select these values for the 2 properties:

image

When you now look at the workflow history of a specific workflow running on a listitem, the page will show you much more detailed information of what is happening in the workflow.

Permanent link to this article: http://www.tonstegeman.com/blog/2009/03/logging-to-the-workflow-history-list-in-sharepoint-workflows/

Mar
09
2009

Redirect in SharePoint using SPUtility.Redirect

I was looking at the Redirect method in the SPUtility class. Besides the url, this method also takes a parameter of type SPRedirectFlags. I was wondering when to use the values of this enumeration. The MSDN documentation does help a bit, but not very much. I asked on Twitter if anyone knew the meaning of these values. Within minutes, Anders answered and pointed me in the right direction. One of the examples of why I love Twitter!

The signature of the Redirect method is:

public static bool Redirect(string url, SPRedirectFlags flags, HttpContext context)
 
There is an overload that also takes the queryString as a parameter. The SPRedirectFlags enumeration has these values:
  • CheckUrl
  • Default
  • DoNotEncodeUrl
  • DoNotEndResponse
  • RelativeToLayoutsPage
  • RelativeToLocalizedLayoutsPage
  • Static
  • Trusted
  • UseSource

Source url

If UseSource is part of the flags parameter of the Redirect method, the url to which the user is redirected will be read from the querystring of the original request (context parameter). The new url will be the value of one of these querystring parameters:

  • Source
  • NextUsing
  • NextPage

If one of these parameters has a value, the new url will be validated. Validation is done by the IsUrlSafeForRedirect method of the Request property of the current SPWeb. The url in the querystring of the original request needs to be a relative url. See the samples below.

If this url is not valid, or the UseSource parameter resulted in an empty string, the url parameter that was originally passed will be used.

Static url

If Static is part of the flags parameter, the url is considered relative. Depending on the presence of RelativeToLayoutsPage in the flags parameter, the url is relative to the ´_layouts´. If this enumeration value is present, SharePoint checks the flags parameter for the presence of RelativeToLocalizedLayoutsPage. If this is present, a new absolute url to the localized ‘_layouts’ folder is constructed. If not, the url is constructed to the root of the ‘_layouts’ folder. The layouts url is the url of the current SPWeb, followed by ‘_layouts’ and the Language of the current SPWeb. If constructing this url fails for whatever reason, the url will be the url of the current SPSite. If required, the value of SPGlobal.ServerCulture.LCID is added to the url.

Absolute url

If Static is NOT part of the flags parameter, the user will be redirected to the value of the url parameter, after validating the url. If Trusted is part of the flags parameter, the url is always valid. If Trusted is not available, it depends on the outcome of the IsUrlSafeForRedirect method of the Request property of the current SPWeb whether or not the url is valid.

Encoding

The last step is before the user is redirected is the encoding. If DoNotEncodeUrl is NOT present in the flags attribute, the url is first encoded using SPHttpUtility.UrlPathEncode.

Samples

Below you will find a number of samples. Each sample starts with the sample code for SPUtility.Redirect. This code is tested in a web part. The 3 lines below that sample show the results of calling the redirect. The first column contains the page url that contains the web part. The second column contains the result of the redirect.

SPUtility.Redirect(http://newsite, SPRedirectFlags.Default, HttpContext.Current);
http://intranet/site1/Pages/default.aspx no redirect
http://intranet/site1/Pages/default.aspx?Source=http://newsite no redirect
http://intranet/site1/Pages/default.aspx?Source=/news no redirect
 
SPUtility.Redirect("/news", SPRedirectFlags.Default, HttpContext.Current);
http://intranet/site1/Pages/default.aspx http://intranet/news
http://intranet/site1/Pages/default.aspx?Source=http://newsite http://intranet/news
 http://intranet/site1/Pages/default.aspx?Source=/news http://intranet/news
 
SPUtility.Redirect(http://newsite, SPRedirectFlags.Static, HttpContext.Current);
http://intranet/site1/Pages/default.aspx?Source=http://newsite no redirect
http://intranet/site1/Pages/default.aspx http://newsite
 
SPUtility.Redirect(http://newsite, SPRedirectFlags.UseSource, HttpContext.Current);
http://intranet/site1/Pages/default.aspx no redirect
http://intranet/site1/Pages/default.aspx?Source=http://newsite no redirect
http://intranet/site1/Pages/default.aspx?Source=/news http://intranet/news
 
SPUtility.Redirect(http://newsite, SPRedirectFlags.UseSource | SPRedirectFlags.Trusted, HttpContext.Current);
http://intranet/site1/Pages/default.aspx no redirect
http://intranet/site1/Pages/default.aspx?Source=http://newsite http://newsite
http://intranet/site1/Pages/default.aspx?Source=/news http://intranet/news
 
SPUtility.Redirect(http://newsite, SPRedirectFlags.Trusted, HttpContext.Current);
http://intranet/site1/Pages/default.aspx http://newsite
http://intranet/site1/Pages/default.aspx?Source=http://newsite http://newsite
http://intranet/site1/Pages/default.aspx?Source=/news http://newsite
 
SPUtility.Redirect("settings.aspx", SPRedirectFlags.Static | SPRedirectFlags.RelativeToLayoutsPage, HttpContext.Current);
http://intranet/site1/Pages/default.aspx http://intranet/site1_layouts/settings.aspx
http://intranet/site1/Pages/default.aspx?Source=http://newsite http://intranet/site1_layouts/settings.aspx
http://intranet/site1/Pages/default.aspx?Source=/news http://intranet/site1_layouts/settings.aspx
 
SPUtility.Redirect("images/approve.gif", SPRedirectFlags.Static | SPRedirectFlags.RelativeToLayoutsPage | SPRedirectFlags.RelativeToLocalizedLayoutsPage, HttpContext.Current);
http://intranet/site1/Pages/default.aspx http://intranet/site1_layouts/1033/images/approve.gif
http://intranet/site1/Pages/default.aspx?Source=http://newsite http://intranet/site1_layouts/1033/images/approve.gif
http://intranet/site1/Pages/default.aspx?Source=/news http://intranet/site1_layouts/1033/images/approve.gif
 
 
There are a few members of the enumeration that I did not describe, because I was not able to find out how these are used.

Permanent link to this article: http://www.tonstegeman.com/blog/2009/03/redirect-in-sharepoint-using-sputility-redirect/

Feb
26
2009

SharePoint security: an alternative way to manage workspace users

In my current SharePoint project we are building a mixed knowledge sharing / collaboration solution in MOSS. Each site collection is based on the MOSS Collaboration Portal template. Collaboration is supported in a number of different types of workspaces. Each type of workspace has it’s own start site. This site contains a SharePoint list with a custom workflow associated. This workflow creates the workspaces, based on a custom template. After creating the site, the workflow also configures the security for the workspace. In this post I will describe how we have designed the security model. We have implemented a custom way to add users to user groups for these workspaces. I think the ideas behind this solution could apply to your project(s) as well, so I decided to write it down. In this post I will describe what we have built, in the next post I will describe how we did it.

General security model

Our security model is based around roles. For every role we have created a SharePoint group. No user or SharePoint group (except the site collection admins) gets the permissions to change security settings. By setting the ownership of these groups the correct way, we grant people permissions to add users to certain groups. These general groups are used to set the appropriate permissions throughout the site collection. The screenshot below shows an example.

image

There is nothing special about this. It is all out of the box SharePoint configuration. Every user in the Active Directory can become a member of the SharePoint groups above.

Workflow and workspace groups

One of the types of collaboration workspaces is the Project Workspace. Our site collection has a site called Project Workspaces. This site has a list that holds the project registrations. This list has a User or Group field called Project Leaders. Users with permissions to add new projects can pick the project leaders from the general Project Leaders group. The screenshot below shows part of this list.

image

Attached to this list is a custom built workflow. This workflow creates and configures a new project workspace after an item is added to the list. After creating the site, the workflow applies the security model to the new workspace. For this specific workspace, 3 new SharePoint groups are created. These 3 groups get specific permissions in this workspace and they are configured in the People and Groups Quick Launch. This is shown in the screenshot below:

image

This way project leaders for this specific project (the members of the group “Documentthenewsecuritymodel Project Managers”) always have the groups they need to work with for their project available with just a few mouse clicks. The user who initially created the project and the project leaders that are selected in the list item are added as members of this group by the workflow. Again, nothing special here. You can do this all by clicking your way though SharePoint.

What is new 1. – Add new users to the groups

The first custom part of our solution is the way users are added to the user groups. When users click the New button, or the Add User button on the People and Groups page, they are redirected to a custom page. In this page, they first need to select one of the available SharePoint groups. After selecting one of the groups, they can use the out of the box People Picker to select users from that group. The screenshot below shows this page:

image

The groups that are available for the user to pick members from (Visitors, Members, Project Leaders) are the SharePoint groups that at least have the View Items permission level at the parent site of the workspace. In the screenshot below you can see that the 3 groups in the screenshot above are the only groups that have permissions set at the Project Workspaces site.

image

There are 2 reasons to build this custom Add Users page:

  1. Our security model is getting more restrictive the deeper you get into our site hierarchy. The number of users that have access is getting less and less. Until you are going to use the out of the box ‘Add User’ page to add users to the workspace groups. This page allows users to pick any user with an account in the Active Directory. Whether or not this user has access to the site collection, you can add him/her to one of the specific workspace groups. In our case, this situation was not acceptable. Project Leaders are the only users with permissions to add users to the workspace groups. They should just be able to pick people that have access to that specific type of workspace. In this example I showed the Project Workspaces, so only users with access to the Project Workspaces site can get access to the workspaces.
  2. The Activy Directory is pretty big. There are a lot users to select from and you can only find people by searching them. You cannot pick users from a list. In our custom page, the project leader can select the members from the group he/she has picked:
    image 
    This makes life of the project leaders a lot easier.
What is new 2. – Participants page

The other new feature in our workspaces is the Participants page. Every workspace has a navigation link to the participants page. This page shows the relevant groups for that specific workspace. The screenshot below shows this page for our example workspace.

image

The page shows the 3 relevant SharePoint groups for this workspace. We are using the out of the box Site Users web part for this. The out of the box web part however has a minor issue. If you configure it to use a specific SharePoint group, and you click the Add new user link at the bottom of the web part, you end up at the Add User page, but in the first group that is configured in the Quick Launch. Users then need to use the Add users to a SharePoint group dropdown to select the group they were trying to add users to. This is fixed in our participants page. The link takes you straight to the Add Users page with your group selected.

Instead of just the out of the box Add new user link, the web parts on the participants page also have a link called Manage users. This takes the user straight to People and Groups.

The reason for creating this custom participants page, is that it is much easier for project leaders. This page is easier for them to manage the workspace users. In 1 overview, they can see the membership of the relevant groups and easily add users to these groups. And it makes it easy for other members of the workspace to see who is in which role for this project.

What is new 3. – E-mail users

In the screenshot above you probably also noticed the Site Users web part now has a link called E-mail users. This link can be used, just like the out of the box E-mail Users option in the Actions menu of the People and Groups page. Our custom page makes it easier to use this. It takes the user directly to that specific group. All members are automatically selected, and the user just needs to select the e-mail link from the Actions menu:

image

Conclusion

In  this post I described a real life SharePoint security model and some enhancements. These enhancements are necessary to enforce our model and make it easier to work with. I have seen SharePoint being used a lot like I briefly described here and I think the enhancements described here can really help in these situations. I hope they inspire you to think about the applicability for your situation. In the next post I will describe how we have built these enhancements.

Permanent link to this article: http://www.tonstegeman.com/blog/2009/02/sharepoint-security-an-alternative-way-to-manage-workspace-users/

Feb
20
2009

Storing a collection of objects in the hierarchical object store

For one of the projects that I am working on, we had to store some additional configuration on audiences. After reading the instruction and warning by Robin, I decided the Hierarchical Object Store was the place to save the configuration. Instead of persisting one object, I had to save a number of objects. Inspired by this example I managed to do so by using SPPersistedChildCollection.

First thing to do was to create the object to persist. This inherits from SPPersistedObject:

public class StoredAudienceInfo : SPPersistedObject
{
    [Persisted]
    private Boolean _selected;
    public Boolean Selected
    {
        get { return _selected; }
        set { _selected = value; }
    }

    public StoredAudienceInfo()
    {
    }

    public StoredAudienceInfo(Audience audience, SPPersistedObject parent)
        : base(audience.AudienceName, parent, audience.AudienceID)
    {
    }

}
Make sure your object has a parameterless constructor. By using the Persisted attribute, you specify which members of your class are (de)serialized and saved in the object store.
Please note:  do not change the name of this class after you persisted objects to the configuration database. If you do this (like I did), you will get “Object reference not set to an instance of an object” when the objects are loaded.
I defined an extra constructor, making it easier to work with the audiences in code.
 
Next thing was to create the store, that will hold multiple instances of the StoredAudienceInfo class.
public class AudienceInfoStore : SPPersistedChildCollection<StoredAudienceInfo>
{
    public AudienceInfoStore()
        :base(SPFarm.Local)
    {
    }

    public StoredAudienceInfo CreateNew(Audience audience)
    {
        return new StoredAudienceInfo(audience, SPFarm.Local);
    }

    public bool Contains(Audience audience)
    {
        return (audience!=null && GetValue<StoredAudienceInfo>(audience.AudienceID) != null);
    }
}

 

The additional audience info is stored in the configuation database at the farm level. The other option you have is to save it in the store for a web application. The store has a method CreateNew, that returns a new object to store, based on the audience that I pass as parameter. Now I can easily add objects to the store like this:

AudienceManager manager;
manager = new AudienceManager(ServerContext.GetContext(this.Context));
foreach (Audience audience in manager.Audiences)
{
    if (audience.AudienceID!=Guid.Empty && !Store.Contains(audience))
    {
        StoredAudienceInfo info = Store.CreateNew(audience);
        info.Selected = false;
        info.Update();
    }
}

private AudienceInfoStore Store
{
    get
    {
        if (_store == null)
            _store = new AudienceInfoStore();
        return _store;
    }
}

And to load the store, I simply create a new object of the AudienceInfoStore class. That’s it. Nice and easy. If you are going to use this a lot, you might want to check this article by Margriet and Nikander Bruggeman. I did not have the issues that they in their performance test, but in our project ths load is limited.

Permanent link to this article: http://www.tonstegeman.com/blog/2009/02/storing-a-collection-of-objects-in-the-hierarchical-object-store/

Jan
24
2009

My First Silverlight part 4 – Create the Silverlight application

This is the final part of a series on Silverlight development in SharePoint. In the first part I have described how to solution works for a user and how to prepare your SharePoint server and development machine for Silverlight development. The second post describes how to create the WCF service that gets the news items from SharePoint. In the third post we have created a web part this will act as a host for our Silverlight application. And this is the final part. In this part we finally get to doing some Silverlight work. As I have described in the first post, this article is not about Silverlight itself, but mainly about creating a working solution in SharePoint that uses SharePoint data.

Step 1 – Create a new project

We start off again with adding a new project to our Visual Studio solution. We now select the Silverlight Application template.

image

Next thing to do is to add a service reference to our WCF service. Right click the project and click Add Service Reference. In the dialog that appears, click the little arrow next to the Discover button. Select Services in Solution:

image

If you used the correct Visual Studio template while creating your WCF service, you should now be able to select the news service. Give your reference a name and click OK.

Please note: If you start with the wrong WCF project template (like I did, I used a regular class library first), it is more difficult to get the right service reference. After building and deploying my WCF assembly and SVC file, I tried to add a service reference by entering the url to my url in the LAYOUTS folder. Visual Studio then asked for my Discovery Credential, but I was not able to login. My problem was that I do not have anonymous access enbled on my web application and therefore Visual Studio asked me to login. One way to get around this is to start Fiddler before you click the Add Service Reference button. Fiddler then takes care of logging in and you will be able to add the service reference.

image

But if you use the correct template, you should not have this issue. And I no longer have it, thanks to Donald, who pointed me to the little arrow button.

Step 2 – Configure the WCF client

Before we can deploy our Silverlight control and web part to SharePoint, we need to configure the client binding and endpoint to the WCF service. We will need to do this in the web.config file for our SharePoint web application. I have added this system.servicemodel to my web.config file. In this configuration, the binding information is set. We use basicHttpBinding to connect to our WCF service. If this is the correct binding for you to use, depends on a number of factors. Besides the binding, the servicemodel element configures the endpoint. This endpoint selects the binding we previously configured and it specifies the contract to be used. Apart from that the address of our WCF service is configured in the config file.

<system.serviceModel>
  <bindings>
    <basicHttpBinding>
      <binding name="NewsService" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">
        <security mode="TransportCredentialOnly" />
      </binding>
    </basicHttpBinding>
  </bindings>
  <client>
    <endpoint
        address="http://moss/_layouts/sbb/TST.BlackBelt.Silverlight.Services.News.svc"
        binding="basicHttpBinding"
        bindingConfiguration="NewsService"
        contract="NewsService.NewsService"
        name="NewsService" />
  </client>
</system.serviceModel>
 
The reason that I show this configuration before showing you how to use it, is that this configuration has an issue. as you can see, the endpoint is configured to connect to the WCF service at http://moss/_layouts/sbb/TST.BlackBelt.Silverlight.Services.news.svc. This has to be an absolute url. You cannot configure it to be relative. This means that your Silverlight application will always connect on this url and we will loose the context of the web part. Which we needed, to get the right news items, depending on the Scope setting of the user. This is solved by using dynamic binding configuration, which is covered in the next step.

Step 3 – Get the scope and filter values

When configuring the web part, we have selected one of the three available scopes. This scope value is passed to this Silverlight application by using the InitParameters property of the Silverlight control. The same applies to the filter value that was set by connecting a filter web part. We now first need to get the value of the scope and filter init parameters. To access these parameters, we use the InitParams property of the StartupEventArgs parameter in the Startup event. First open the cs file for App.xaml. In the constructor of the Page object, register a new event handler for the Startup event. In that event, save the InitParams value of the ‘e’ parameter. I added a readonly property ApplicationParameters to the App object to access these parameters:
 
public partial class App : Application
{
    IDictionary<string, string> _parameters;

    public App()
    {
        this.Startup += this.Application_Startup;
        InitializeComponent();
    }

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        _parameters = e.InitParams;
        this.RootVisual = new Page();
    }

    internal IDictionary<string, string> ApplicationParameters
    {
        get { return _parameters; }
    }
}
We now have our Scope and Filter values available. I have implemented 2 properties on my Silverlight Page object to access these values:
public NewsService.NewsScope Scope
{
    get
    {
        if (((App)App.Current).ApplicationParameters == null)
            return NewsService.NewsScope.CurrentWeb;
        string scope = ((App)App.Current).ApplicationParameters["Scope"];
        return (NewsService.NewsScope)Enum.Parse(typeof(NewsService.NewsScope), scope, true);
    }
}

private NewsService.NewsFilter Filter
{
    get
    {
        if (((App)App.Current).ApplicationParameters == null)
            return null;
        if (((App)App.Current).ApplicationParameters.ContainsKey("FilterField") &&
            ((App)App.Current).ApplicationParameters.ContainsKey("FilterValue"))
        {
            string fieldName = ((App)App.Current).ApplicationParameters["FilterField"];
            string val = ((App)App.Current).ApplicationParameters["FilterValue"];
            if (!string.IsNullOrEmpty(fieldName) && !string.IsNullOrEmpty(val))
            {
                NewsService.NewsFilter newFilter = new NewsService.NewsFilter();
                newFilter.FieldName = fieldName;
                newFilter.Value = val;
                return newFilter;
            }
        }
        return null;
    }
}

Step 4 – Configure the AgDataGrid and get the data

In the Silverlight application I have used the agDataGrid. This is a free to use Silverlight control by DevExpress. It is a very easy to use grid. Through this link, you can find a number of interesting videos that help you to get started with using that control. After adding the references required, I have added the Grid to my page xaml:

<Grid x:Name="LayoutRoot" Background="White">
    <StackPanel Orientation="Vertical">
        <TextBlock x:Name="labelSite"></TextBlock>
        <grid:AgDataGrid x:Name="newsGrid" ColumnsAutoWidth="True" ShowGroupPanel="Visible" Height="500"
                         PreviewTemplate="{StaticResource newsTemplate}" FocusedRowChanged="newsGrid_FocusedRowChanged">
            <grid:AgDataGrid.Columns>
                <grid:AgDataGridDateColumn FieldName="Modified"></grid:AgDataGridDateColumn>
                <grid:AgDataGridTextColumn FieldName="Title" Width="200"></grid:AgDataGridTextColumn>
                <grid:AgDataGridTextColumn FieldName="Author"></grid:AgDataGridTextColumn>
                <grid:AgDataGridColumn FieldName="Year"></grid:AgDataGridColumn>
                <grid:AgDataGridTextColumn FieldName="Month"></grid:AgDataGridTextColumn>
            </grid:AgDataGrid.Columns>
        </grid:AgDataGrid>
    </StackPanel>
</Grid>

In the Columns element, I have added the columns of the grid. The FieldName attribute references a public property of my NewsItem objects. Now it is time to connect to the service and get some news items. To do that, I added a new method GetNews to my Page object and call that from the constructor. First thing to do in the GetNews method is to configure a new binding object. As we have seen in the previous step, the binding is configured in the servicemodel configuration in web.config. We need to create a new binding at runtime however, to make sure we call our WCF service in the correct context. To get the new address for the binding, we get the url to the xap file from App.Current.Host.Source and construct a new url to the WCF service. Which in this case means that the WCF service and the XAP file need to deployed to the same folder.

public Page()
{
    InitializeComponent();
    GetNews();
}

private void GetNews()
{
    BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
    string context = App.Current.Host.Source.ToString().Substring(0, App.Current.Host.Source.ToString().ToLower().IndexOf("_layouts/"));

    Uri uri = new Uri(new Uri(context), "_layouts/sbb/TST.BlackBelt.Silverlight.WCFServices.News.svc");
    EndpointAddress address = new EndpointAddress(uri);

    NewsService.NewsServiceClient newsClient = new NewsService.NewsServiceClient(binding, address);
    NewsService.NewsFilter filter = this.Filter;
    if (filter == null)
    {
        newsClient.GetNewsItemsCompleted += new EventHandler<TST.BlackBelt.Silverlight.News.NewsService.GetNewsItemsCompletedEventArgs>(newsClient_GetNewsItemsCompleted);
        newsClient.GetNewsItemsAsync(Scope);
    }
    else
    {
        newsClient.GetNewsItemsFilteredCompleted += new EventHandler<TST.BlackBelt.Silverlight.News.NewsService.GetNewsItemsFilteredCompletedEventArgs>(newsClient_GetNewsItemsFilteredCompleted);
        newsClient.GetNewsItemsFilteredAsync(Scope, filter);
    }
}

Because every call to a service is asynchronous, the next thing to do is add event handlers for the 2 GetNewsItems methods. After adding the event handlers, the Page object will call one of the 2 asynchronous functions, depending on the availability of a filter. The code snippet below shows the implementation of these event handlers:

void newsClient_GetNewsItemsFilteredCompleted(object sender,
             TST.BlackBelt.Silverlight.News.NewsService.GetNewsItemsFilteredCompletedEventArgs e)
{
    newsGrid.DataSource = e.Result;
}

void newsClient_GetNewsItemsCompleted(object sender,
              TST.BlackBelt.Silverlight.News.NewsService.GetNewsItemsCompletedEventArgs e)
{
    newsGrid.DataSource = e.Result;
}

They are pretty simple, they just bind the result of the WCF service to our grid.

Step 5 – Implement the row preview

As we have seen in the first part of this series, users can select a row in the grid by clicking it. The row then expands and shows extra information about the announcement. This is an out of the box feature of the AgDataGrid. the DevExpress site has an excellent video on how to use this. Basically you add a new resource to your Silverlight app (I have included it in the page.xaml) and point the PreviewTemplate property to this resource. See the xaml snippet in step 4. This is the xaml for that template:

<UserControl.Resources>
    <DataTemplate x:Key="newsTemplate">
        <Grid x:Name="ItemDetails" Background="White">
            <Grid.RowDefinitions>
                <RowDefinition Height="25"></RowDefinition>
                <RowDefinition Height="25"></RowDefinition>
                <RowDefinition Height="25"></RowDefinition>
                <RowDefinition Height="25"></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <TextBlock Text="Created" Grid.Row="0" Grid.Column="0"></TextBlock>
            <TextBlock Text="{Binding Created}" Grid.Row="0" Grid.Column="1"></TextBlock>
            <TextBlock Text="Created by" Grid.Row="1" Grid.Column="0"></TextBlock>
            <TextBlock Text="{Binding CreatedBy}" Grid.Row="1" Grid.Column="1"></TextBlock>
            <TextBlock Text="Last modified" Grid.Row="2" Grid.Column="0"></TextBlock>
            <TextBlock Text="{Binding Modified}" Grid.Row="2" Grid.Column="1"></TextBlock>
            <TextBlock Text="Last modified by" Grid.Row="3" Grid.Column="0"></TextBlock>
            <TextBlock Text="{Binding Author}" Grid.Row="3" Grid.Column="1"></TextBlock>
        </Grid>
    </DataTemplate>
</UserControl.Resources>

It creates a new grid with 4 rows and 2 columns. After declaring the RowDefinitions and the ColumnsDefinitions, you create a number of TextBlock controls and add these to the grid.

Step 6 – Implement the item preview

Last thing to do to finialize our Silverlight News project, is to add the preview for the news item itself. The challenge for this is that the content of this news item is HTML. Silverlight cannot display HTML directly. Therefore I use a technique called a HTML bridge. As you have seen in the previous post, that webpart has added a HTML DIV caled ‘contentDiv’ to the page. The AgDataGrid has an event called FocusedRowChanged. We implement an event handler for that event:

private void newsGrid_FocusedRowChanged(object sender, DevExpress.Windows.Data.FocusedRowChangedEventArgs e)
{
    int currentIndex = e.NewRowHandle;
    if (currentIndex >= 0)
    {
        AgDataGrid grid = sender as AgDataGrid;
        NewsService.NewsItem item = grid.GetDataRow(currentIndex) as NewsService.NewsItem;
        HtmlDocument htmlDocument = HtmlPage.Document;
        HtmlElement contentDiv = htmlDocument.GetElementById("contentDiv");
        contentDiv.SetStyleAttribute("left", "300px");
        contentDiv.SetStyleAttribute("top", string.Format("{0}px", currentIndex*30 + 120));
        contentDiv.SetStyleAttribute("width", "300px");
        contentDiv.SetStyleAttribute("height", "300px");
        contentDiv.SetAttribute("innerHTML", item.Body);
    }
}

In this handler we get a reference to the current selected news item and a reference to the HtmlDocument in which our Silverlight control is used. Then we search for the contentDiv and set the innerHtml attribute of that DIV to the value of the Body property of the newsitem. What you also need to do is set the top of your DIV, to make the DIV move with the current selected row.

If you want to learn more about Html bridging, read the quickstart, or this FAQ.

Step 7 – Deploy the XAP file

The last thing to do is to deploy the xap file. There are several places where you can deploy a xap file. I deploy the xap file to the LAYOUTS folder, because it is easy to deploy from my WSP file.

Wrap up

That’s it for now. In this series of 4 blog posts we have created a Silverlight News web part that can be configured using the normal SharePoint web part configuration. It is aware of it’s context in SharePoint and is able to handle incoming filters. I hope you enjpyed reading it. I certainly enjoyed creating and writing it. I also hope it inspired you to write Silverlight applications in SharePoint. I think Silverlight is great technology, and it is very powerful in combination with SharePoint. If you know how to integrate both technologies. I hope I have achieved that in this series. If you have any questions, please let me know. And I would also be interested in seeing what SharePoint solution you will build in Silverlight.

Permanent link to this article: http://www.tonstegeman.com/blog/2009/01/my-first-silverlight-part-4-create-the-silverlight-application/

Jan
22
2009

My first Silverlight part 3 – Create the Silverlight web part

This is the 3rd part in a small series on Silverlight development for SharePoint. In part 1 I have introduced the Silverlight News web part and showed you how to prepare your server(s) for Silverlight. The 2nd part shows how to create and install the WCF service that will search for the news items. In this part we will create the web part that will host the Silverlight solution. The main task is to host and parameterize the Silverlight control in the page. It also allows users to select a scope and it makes the web part act as a filter consumer. This web part gathers all the information required for the Silverlight application to work properly.

Step 1 – Create a new web part

I added a new class library to my solution and added a new webpart class to the new project. This project needs 2 new references; a reference to Microsoft.SharePoint.dll and a reference to System.Web.Silverlight.dll. The web part has 1 public property, for the scope of the news, and a number of private variables that are needed to host the Silverlight control:

public class NewsWebPart : WebPart, IWebEditable
{
    private ScriptManager _scriptHandler;
    private System.Web.UI.SilverlightControls.Silverlight _silverlightControl = null;

    [Browsable(false),
    Personalizable(PersonalizationScope.Shared)]
    public NewsScope Scope
    {
        get;
        set;
    }

    public NewsWebPart()
    {
    }
}

The NewScope enumeration is defined as:

[DataContract(Namespace = "http://schemas.blackbelts.com/2008/11/news")]
public enum NewsScope : int
{
    [EnumMember()]
    CurrentWeb = 0,

    [EnumMember()]
    CurrentWebAndBelow = 1,

    [EnumMember()]
    SiteCollection = 2
}

Because the  enum will be passed between the Silverlight client and the WCF service, the enum is also decorated with the DataContract attribute.

Step 2 – Create an editor part

Second thing to do is to create an editor part that will let our users select the scope for their news search.

public class NewsWebPartEditor: EditorPart
{
    private const string EMPTYITEM = "EMPTY";
    private RadioButtonList _selectScope;

    public NewsWebPartEditor(string webPartID)
    {
        this.ID = string.Format("SelectWebPropertyEditor{0}", webPartID);
        this.Title = "Silverlight News Options";
    }

    protected override void CreateChildControls()
    {
        Label label = new Label();
        label.Text = "Select a scope:";
        Controls.Add(label);

        _selectScope = new RadioButtonList();
        _selectScope.Items.Add(new ListItem("Current web", NewsScope.CurrentWeb.ToString()));
        _selectScope.Items.Add(new ListItem("Current web and below", NewsScope.CurrentWebAndBelow.ToString()));
        _selectScope.Items.Add(new ListItem("Site Collection", NewsScope.SiteCollection.ToString()));
        Controls.Add(_selectScope);
        base.CreateChildControls();
    }

    public override bool ApplyChanges()
    {
        NewsWebPart webpart = WebPartToEdit as NewsWebPart;
        webpart.Scope = (NewsScope)Enum.Parse(typeof(NewsScope), _selectScope.SelectedValue);
        return true;
    }

    public override void SyncChanges()
    {
        EnsureChildControls();
        NewsWebPart webpart = WebPartToEdit as NewsWebPart;
        _selectScope.SelectedValue = webpart.Scope.ToString();
    }
}

If you want to read more about creating an editor part, read this post I have written about this. Besides creating the editor part, we will need to register it in the NewsWebPart by implementing IWebEditable and implementing CreateEditorParts:

EditorPartCollection IWebEditable.CreateEditorParts()
{
    List<EditorPart> newEditors = new List<EditorPart>();

    EditorPartCollection editors = base.CreateEditorParts();
    foreach (EditorPart part in editors)
        newEditors.Add(part);

    newEditors.Add(new NewsWebPartEditor(this.ID));

    return new EditorPartCollection(newEditors);
}

Step 3 – Initialize and create the controls

In the OnInit, our Silverlight host web part registers a new script manager.

protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    _scriptHandler = ScriptManager.GetCurrent(this.Page);
    if (_scriptHandler == null)
    {
        _scriptHandler = new ScriptManager();
        _scriptHandler.ID = string.Format("TST.BlackBelt.Silverlight.News.script_{0}", this.ID);
        this.Controls.Add(_scriptHandler);
    }
}

This scriptmanager is required on the page for the Silverlight control to work properly. In CreateChildControls our web part will create and initialize the Silverlight control. The Source of the Silverlight control points to the xap file that we will create in the next post. Please note that the xap file is deployed to the LAYOUTS folder, which is a folder relative to our current context. The InitParameters property of the silverlight control can be used to initialize the silverlight control. I use the InitParameters to send the title of the web part and the selected scope to the silverlight control. This is important to understand, because once you are in your silverlight control, you are in a totally different request, that knows nothing about SharePoint, web parts or web part properties. Therefore I send all parameter I need in my silverlight control by setting the InitParameters property.

protected override void CreateChildControls()
{
    base.CreateChildControls();

    _silverlightControl = new System.Web.UI.SilverlightControls.Silverlight();
    _silverlightControl.ID = string.Format("TST.BlackBelt.Silverlight.News.xap_{0}", ID);
    _silverlightControl.Source = string.Format("{0}/{1}", SPContext.Current.Web.Url, "_layouts/sbb/TST.BlackBelt.Silverlight.News.xap");

    if (this.Width.Value > 0)
        _silverlightControl.Width = this.Width;
    else
        _silverlightControl.Width = Unit.Pixel(600);

    if (this.Height.Value > 0)
        _silverlightControl.Height = this.Height;
    else
        _silverlightControl.Height = Unit.Pixel(400);
    _silverlightControl.InitParameters = string.Format("WebPartTitle={0}, Scope={1}", this.Title, this.Scope.ToString());
    _silverlightControl.Attributes["Style"] = "z-index:0";
    _silverlightControl.Windowless = true;
    this.Controls.Add(_silverlightControl);

    LiteralControl contentDiv = new LiteralControl("<div id=\"contentDiv\" style=\"position:absolute;z-index:1;border:1px solid black;background-color:white\"></div>");
    this.Controls.Add(contentDiv);
}
 
The last control that is created by our web part and added to the controls collection is a DIV, called the contentDiv. This DIV will be used to render the contents of the news annoucement after the user has selected an item in the interface. See the screenshots in the first article.

Step 4 – Make our web part a filter consumer

Last thing to implement in our web part is the filter consumer. Our web part will accept incoming filter values from filter providers like the out of the box filter web parts, or my List Item Filter or Web Context Filter web parts. To do this, we create a new method and add the ConnectionConsumer attribute. In the SetFilter method, we create a new ConsumerParameter. These parameters can be selected by the user upon connection the web part to a filter provider. In this example web part, we currently only support the Title field. So the webpart can currently only be filtered on the Title field. In the SetFilter web also handle the incoming filter. In this case the filter field and value are added to the InitParameters property of the Silverlight control. Make sure this control is properly initialized by using EnsureChildControls in the SetFilter method. The incoming filter value is added to the initialization parameters for the silverlight control. This controls passes the filter value to the WCF service that we created in the second post. This WCF service processes the filter.

[ConnectionConsumer("filter", "IFilterValues", AllowsMultipleConnections = true)]
public void SetFilter(Microsoft.SharePoint.WebPartPages.IFilterValues filterValues)
{
    if (filterValues != null)
    {
        EnsureChildControls();
        List<Microsoft.SharePoint.WebPartPages.ConsumerParameter> parameters = new List<Microsoft.SharePoint.WebPartPages.ConsumerParameter>();
        parameters.Add(new Microsoft.SharePoint.WebPartPages.ConsumerParameter(
            "Title",
            Microsoft.SharePoint.WebPartPages.ConsumerParameterCapabilities.SupportsSingleValue |
            Microsoft.SharePoint.WebPartPages.ConsumerParameterCapabilities.SupportsAllValue |
            Microsoft.SharePoint.WebPartPages.ConsumerParameterCapabilities.SupportsEmptyValue));
        filterValues.SetConsumerParameters(
            new System.Collections.ObjectModel.ReadOnlyCollection<Microsoft.SharePoint.WebPartPages.ConsumerParameter>(parameters));
        if (filterValues.ParameterValues != null && filterValues.ParameterValues.Count > 0)
        {
            HandleFilter(filterValues.ParameterName, filterValues.ParameterValues[0]);
        }
    }
}

private void HandleFilter(string fieldname, string filter)
{
    _silverlightControl.InitParameters = string.Format("WebPartTitle={0}, Scope={1}, FilterField={2}, FilterValue={3}", this.Title, this.Scope.ToString(), fieldname, filter);
}

If you want to learn more about creating your own filter providers and consumers, you can start by reading this blog post.

Permanent link to this article: http://www.tonstegeman.com/blog/2009/01/my-first-silverlight-part-3-create-the-silverlight-web-part/

Jan
21
2009

My first Silverlight part 2 – Create the WCF news service

In part 1 of this series I introduced the Silverlight News web part and described all steps necessary to prepare your SharePoint servers and development machine. In this post I will describe how I created the WCF service. WCF is one of the ways to get data out of SharePoint to your Silverlight client. Using the out of the box SharePoint web services is another. But SharePoint does not have a web service to aggregate data from multiple sites into 1 single view. So I had to create my own service anyway. Because I was also behind on getting up to speed on WCF, I decided to build a WCF service. I started off with starting Visual Studio and create a new project of type “WCF Service Library”:

image

You can also create a class library and add a svc file manually. The advantage of using a WCF Service Library is that you can reference the service from the Silverlight client as a project reference in Visual Studio directly. I first started off with a class library and referenced the service after deploying it to SharePoint. This gave me some authentication headaches, so I switched to the Service Library which made the service reference work instantly.

Step 1 – Define the service contract

In this step we define the interface for the service. This is an abstract defnition of what the service will look like. By decorating the interface with the “ServiceContract” attribute from the System.ServiceModel namespace, we mark our interface as a contract for a WCF interface. The methods that our service will support need to be decorated with the “OperationContract” attribute. Currently my service definition looks like this:

[ServiceContract(Name = "NewsService", Namespace = "http://schemas.blackbelts.com/2008/11/news")]
public interface INewsService
{
    [OperationContract]
    String SiteTitle();

    [OperationContract]
    List<NewsItem> GetNewsItems(NewsScope scope);

    [OperationContract]
    List<NewsItem> GetNewsItemsFiltered(NewsScope scope, NewsFilter filter);
}

It has 3 methods:

  • SiteTitle
    This is a test method that returns the title of a SPWeb. It can be used to test if the WCF service is running in the correct context when called from a client.
  • GetNewsItems
    This function returns all announcement for a scope. The scope can be site, site including sub sites and site collection.
  • GetNewsItemsFiltered
    This returns the news items for the scope, but now only items that are valid according to the NewsFilter that is applied.

Step 2 – Define the data contract

After describing the service in the service contract, we need to describe our objects that are sent to and from the service. There are 2 ways to do this. You can use the DataContractSerializer and the XmlSerializer. I decided to use the first option and describe my objects by using the DataContract and DataMember attributes from the System.Runtime.Serialization namespace. In the example below you will find the definition of the News:

[DataContract(Namespace = "http://schemas.blackbelts.com/2008/11/news")]
public class NewsFilter
{
    [DataMember(IsRequired = true)]
    public string FieldName { get; set; }

    [DataMember(IsRequired = true)]
    public string Value { get; set; }

    public NewsFilter(string fieldName, string value)
    {
        this.FieldName = fieldName;
        this.Value = value;
    }

    public bool IsValid
    {
        get
        {
            return (!string.IsNullOrEmpty(FieldName) && !string.IsNullOrEmpty(Value));
        }
    }

    public string CAMLQuery
    {
        get
        {
            if (!IsValid)
            {
                return string.Empty;
            }
            return string.Format("<Contains><FieldRef Name=\"{0}\" /><Value Type=\"Text\">{1}</Value></Contains>", FieldName, Value);
        }
    }
}
 
The class is marked to be a data member by using the DataContract attribute. The first parameter of this attribute puts the object in the correct namespace. All properties that need to be passed to and from the client are identified by the DataMember attribute. The IsValid and CAMLQuery properties are internal for the service and will not be available in the client. This is called the Opt-in strategy. All visible members are explicitly exposed. The Opt-out strategy by default includes all properties, and you need to exclude the ones that do not need to be part of the data contract. The XmlSerializer uses the opt-out strategy.

Step 3 – Write the implementation

The code snippet below shows the implementation of the NewsService. The code of how to get the news items is not essential for this post, so I skipped that. It will be available in the ZIP file in the last post of this series.

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class NewsService : INewsService
{
    public string SiteTitle()
    {
        return SPContext.Current.Web.Title;
    }

    public List<NewsItem> GetNewsItems(NewsScope scope)
    {
        DataTable announcements = GetAnnouncements(scope);
        return GetNewsItems(announcements);
    }

    public List<NewsItem> GetNewsItemsFiltered(NewsScope scope, NewsFilter filter)
    {
        DataTable announcements = GetAnnouncements(scope, filter);
        return GetNewsItems(announcements);
    }
}
 
Please note the AspNetCompatibilityRequirements attribute the is set on the class. In this attribute, the AspNetCompatibilityRequirementsMode is set to Allowed. The reason for this is that in WCF the HttpContext, and therefore the SPContext, is not available. This is because the WCF service knows nothing about the transport mechanism. By switching on this mode, we will have the HttpContext available in our service.

Step 4 – Create the service file

Next thing to do is to create or update the service file. This file tells which class in which assembly to load when the service is loaded. My svc file looks like this:

<%@ Assembly Name="TST.BlackBelt.Silverlight.WCFServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=28ec3cd925274d6c"%>
<%@ ServiceHost Service="TST.BlackBelt.Silverlight.WCFServices.NewsService"%>

Step 5 – Configure the service

Now we will create a new web.config file the will configure our service. The configuration contains a number of elements that can be found in the example configuration below:

  • ASPNET compatibility mode is enabled in the serviceHostingEnvironment element
  • The bindings element contains the configuration of the binding between server and client.
  • The service element contains the name of the service and the configuration of the endpoint. In WCF there are always 2 endpoints. One for the service and one for the client. This config file will configure the endpoint for the service.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
    <bindings>
      <basicHttpBinding>
        <binding
          name="MOSSBinding"
          bypassProxyOnLocal="true">
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Ntlm" proxyCredentialType="Ntlm" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <services>
      <service
        behaviorConfiguration="TST.BlackBelt.Silverlight.Services.Behavior"
        name="TST.BlackBelt.Silverlight.WCFServices.NewsService">
        <endpoint
          name="NewsService"
          contract="TST.BlackBelt.Silverlight.WCFServices.INewsService"
          address="http://moss/_layouts/sbb/TST.BlackBelt.Silverlight.WCFServices.News.svc"
          binding="basicHttpBinding"
          bindingConfiguration="MOSSBinding"
           />
        <endpoint
          name="CBTmex"
          contract="IMetadataExchange"
          address="mex"
          binding="basicHttpBinding"
          bindingConfiguration="MOSSBinding"
           />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="TST.BlackBelt.Silverlight.Services.Behavior">
          <serviceMetadata httpGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="True" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

The options that are set in the binding depend on your infrastructure. What type of authentication are you using, are you using http or https? How is the service connected through a proxy? This example is the example of my development machine, which is a single server deployment of SharePoint running in a VM.

I found that the configuration of these services is not very easy. The SvcConfigEditor tool can help you with this. And you probably need to read some articles or a WCF book to understand the basic principles.

Step 6 – Deploy the service

Last step before we can test our service is to deploy the service. There are a number of options of where you want to host your WCF services. I have choosen to deploy my service to a sub folder of the LAYOUTS folder. The main reason for this is that I can deploy my service to SharePoint using SharePoint Solution Deployment. Another options is to deploy to the _vti_bin folder. Make sure that you deploy your service to a folder that makes it available as a relative url of the site where you are. Because my service is deployed to LAYOUTS, I can access the service at http://moss/_layouts/sbb/TST.BlackBelt.Silverlight.WCFServices.News.svc, but also at http://moss/news/2009/_layouts/sbb/TST.BlackBelt.Silverlight.WCFServices.News.svc.

My deployment folder contains 3 files. Apart from the service file and the config file, it also contains the silverlight application, the xap file. This is covered in the next post in this series. The assembly is deployed to the GAC.

image

Step 7 – Test the service

Last thing to do is to test if the service is available in SharePoint. If you enter a url to the SVC file that you just deployed to the LAYOUTS folder, your screen must look like this:

image

If you get error messages (I had some, because WCF was pretty new to me…) make sure you have registered the virtual path provider (see previous post) correctly. Otherwise you probably will have some issues in the binding configuration. I found that Fiddler can be a great help here.

Permanent link to this article: http://www.tonstegeman.com/blog/2009/01/my-first-silverlight-part-2-create-the-wcf-news-service/

Jan
20
2009

My First Silverlight part 1 – Introduction and preparation

This is the first article in a small series on Silverlight and SharePoint. In this post I will describe what I have built in Silverlight on top of SharePoint. It is not meant to be something extremely useful, but as a study project to get started with Silverlight development for SharePoint. So this series is not about XAML or how you can create a slick UI, but it will show you how to get SharePoint data displayed in Silverlight, used in a SharePoint web part. In this first article I will describe the solution and how you get your SharePoint server prepared.

Introduction of Silverlight News v1.0

What I have created is a SharePoint web part that aggregates announcements. Just like the Content By Type web part, but now they are displayed in a Silverlight grid. Which looks nicer and users have more options to group and sort, re-arrange columns etc. Here is a screenshot of the web part:

image

When users click on one of the rows, more information about the news item and a preview of the content are displayed:

image

The Silverlight News web part queries for items that have the “Announcement”  content type. In the web part properties you can select the scope for this query. It has 3 options; the current site, the current site and all it’s sub sites and the current site collection. Antoher way to restrict the number of items in the view is to use a SharePoint filter web part. The Silverlight News web part is a filter consumer and therefore you can use any filter web part (also the List Item Filter of Web Context Filter) to filter news items. The screenshot below shows the toolpane of the web part and a filter web part that is sending the filter value to our news web part.

image

Preparing your SharePoint farm(s)

The first step to start working on this is to get your SharePoint server(s) and your development server up to date. On my development machine I went through these installation and configuration steps:

After configuring the development server, I have setup a test server. On that server I installed and configured this:

  • Install MOSS SP1, including the Infrastructure update
  • Install .NET Framework 3.5 SP1
  • Install the Silverlight client
  • Add System.Web.Silverlight.dll to the Global Assembly Cache
  • Modify the web.config file(s)
  • Register MIME type


Preparing for WCF support

My Silverlight client is getting the news items from a WCF service. To be able to run WCF services on SharePoint you need to create and register a virtual path provider. The excellent series on SharePoint and WCF by Sahil Malik has more information on this. First you create the virtual path provider:

public class WCFVirtualPathProvider: VirtualPathProvider
{
    public override bool FileExists(string virtualPath)
    {
        string fixedPath = virtualPath;
        if (fixedPath.StartsWith("~", StringComparison.Ordinal) && fixedPath.EndsWith(".svc", StringComparison.InvariantCultureIgnoreCase))
        {
            fixedPath = fixedPath.Remove(0, 1);
        }
        return Previous.FileExists(fixedPath);
    }

    public override string CombineVirtualPaths(string basePath, string relativePath)
    {
        return Previous.CombineVirtualPaths(basePath, relativePath);
    }

    public override System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType)
    {
        return Previous.CreateObjRef(requestedType);
    }

    public override bool DirectoryExists(string virtualDir)
    {
        return Previous.DirectoryExists(virtualDir);
    }

    public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
    {
        return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
    }

    public override string GetCacheKey(string virtualPath)
    {
        return Previous.GetCacheKey(virtualPath);
    }

    public override VirtualDirectory GetDirectory(string virtualDir)
    {
        return Previous.GetDirectory(virtualDir);
    }

    public override VirtualFile GetFile(string virtualPath)
    {
        return Previous.GetFile(virtualPath);
    }

    public override string GetFileHash(string virtualPath, System.Collections.IEnumerable virtualPathDependencies)
    {
        return Previous.GetFileHash(virtualPath, virtualPathDependencies);
    }

    protected override void Initialize()
    {
        base.Initialize();
    }
}

The base class VirtualPathProvider can be found in the System.Web.Hosting namespace. You need to override all methods and make the change to the FileExists method as in the snippet above. This function removes the ‘~’ and passes the new virtualpath to the other provider(s). This prevents SharePoint from crashing on a WCF request. The SharePoint virtual path provider does not know how to handle request that start with a ‘~’, and you will get an error message.

The second step is to register your new provider. To do this, you need to create a new Http module:

public class WCFVirtualPathProviderRegistration : IHttpModule
{
    static bool _initialized = false;
    static object _locker = new object();

    public void Init(HttpApplication context)
    {
        if (!_initialized)
        {
            lock (_locker)
            {
                if (!_initialized)
                {
                    WCFVirtualPathProvider provider = new WCFVirtualPathProvider();
                    HostingEnvironment.RegisterVirtualPathProvider(provider);
                    _initialized = true;
                }
            }
        }
    }

    public void Dispose()
    {
    }
}

The last thing to do is to register your http module in the web.config. Add the snippet below to the hhtpModules element in the web.config. You need to add it after the other httModules that are registered here.

<add name="WCFVirtualPathProviderRegistration"
     type="TST.WCFServices.WCFVirtualPathProviderRegistration, TST.WCFServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=28ec3cd925274d6c">
</add>

That’s it for the first part. Our SharePoint server(s) are now ready to build the Silverlight News web part. In the next article I will start to describe the WCF News service that aggregates the news items and returns them to the silverlight client.

Permanent link to this article: http://www.tonstegeman.com/blog/2009/01/my-first-silverlight-part-1-introduction-and-preparation/

Jan
12
2009

Introducing the Web Context Filter web part

The web part package that I released yesterday contains a new filter web part. This filter web part is called the “Web Context Filter”. This is a context web part that allow you to use properties of the current web context as a filter value. Any filter consumer in SharePoint like the List View Web Part, Excel Web Access, and the DataView web part can use this value to do something with it. But you can also use it to filter the Content By Type web part, or the List Item Filter web part.

The web part supports the fixed web properties listed below. You can select one of the properties for the current web, the parent web or the root web.

  • Title
  • Name (the url of the site)
  • Language (the language ID)
  • Locale ID (the ID of the locale that is used)
  • Locale name
  • The name of the current user
  • Whether or not the current user is a site collection admin
  • Whether or not the current user is a site admin
  • The name of the author
  • The name of the site collection owner
  • The name of the site template
  • Name and link to the portal (if used)
  • The groupname of associated Visitor, Member and Owner groups

Apart from these fixed properties, the web part allow you to filter on values of properties in the property bag. In an out of the box site, this property bag does not have values. You can have custom workflows or any custom code that add context about the site to the SPPropertyBag. By using the Web Context Filter web part, you can use these values to pass the context to a web part that queries content. If a number of project I have created a custom workflow that provisions project sites for example. The workflow adds some data about that project to the property bag of the site. The Web Context Filter is used to read the project data and pass it to an Excel Services report parameter, that only displays the report for the current project. This way we do not have to publish a specific Excel spreadsheet for each project, but it is driven by the context of the site in which it is used.

The screenshot below shows the editor pane that allow you to select the properties. As you can see, the dropdown shows the name of the property and it’s current value. This helps you to understand what the web part will send as filter value.

image

If you have ideas on how to improve this web part further, please let me know!

Permanent link to this article: http://www.tonstegeman.com/blog/2009/01/introducing-the-web-context-filter-web-part/

Older posts «

» Newer posts