One of my latest challenges was to display one of the new SharePoint 2010 social fields in a datagrid. At first this was just to display the field, but after the client seen the mouse-over functionality on the list item, they wanted to be able to assign a rating straight from the grid without having to go to the item itself. So, here’s how I went about it.

Firstly I created a custom control call as seen in the code sample below:

Rating Control
  1. [DefaultProperty("Rating")]
  2.     [ToolboxData("<{0}:RatingControl runat=server></{0}:RatingControl>")]
  3.     public class RatingControl : WebControl
  4.     {
  5.         protected override void OnInit(EventArgs e)
  6.         {
  7.             base.OnInit(e);
  8.             Page.RegisterRequiresControlState(this);
  9.         }
  10.  
  11.  
  12.         [Bindable(true)]
  13.         [Category("Binding")]
  14.         [DefaultValue("")]
  15.         [Localizable(true)]
  16.         public SPField RatingField
  17.         {
  18.             get
  19.             {
  20.                 SPField field = (SPField)ViewState["RatingField"];
  21.                 return field;
  22.             }
  23.  
  24.             set
  25.             {
  26.                 ViewState["RatingField"] = value;
  27.             }
  28.         }
  29.  
  30.  
  31.         [Bindable(true)]
  32.         [Category("Binding")]
  33.         [DefaultValue("")]
  34.         [Localizable(true)]
  35.         public int ListItemId
  36.         {
  37.             get
  38.             {
  39.                 return Convert.ToInt32(ViewState["ListItemId"]);
  40.             }
  41.  
  42.             set
  43.             {
  44.                 ViewState["ListItemId"] = value;
  45.             }
  46.         }
  47.  
  48.  
  49.         [Bindable(true)]
  50.         [Category("Binding")]
  51.         [DefaultValue("")]
  52.         [Localizable(true)]
  53.         public Guid ListId
  54.         {
  55.             get
  56.             {
  57.                 Guid id = (Guid)ViewState["ListId"];
  58.                 return id;
  59.             }
  60.  
  61.             set
  62.             {
  63.                 ViewState["ListId"] = value;
  64.             }
  65.         }
  66.  
  67.         protected override void CreateChildControls()
  68.         {
  69.  
  70.             if (this.ChildControlsCreated)
  71.                 return;
  72.  
  73.             
  74.             BaseFieldControl renderingcontrol = this.RatingField.FieldRenderingControl;
  75.             renderingcontrol.ControlMode = SPControlMode.Display;
  76.             renderingcontrol.ListId = this.ListId;
  77.             renderingcontrol.ItemId = this.ListItemId;
  78.             Controls.Add(renderingcontrol);
  79.             this.ChildControlsCreated = true;
  80.            
  81.            
  82.             
  83.        }

Next, after adding a new template column to the datagrid containing a place holder (DataGrid source), then binding the data source to the ListItemCollection returned. 

DataGrid Source
  1. <asp:TemplateColumn>
  2.          <HeaderTemplate>
  3.                 Rating
  4.             </HeaderTemplate>
  5.             <ItemTemplate>
  6.                 <asp:PlaceHolder runat="server" ID="RatingPlaceHolder"></asp:PlaceHolder>
  7.             </ItemTemplate>
  8.         </asp:TemplateColumn>

 

I create a new instance  of the rating control on the girdview OnItemDataBound event receiver and add it to the place holder.

OnItemDataBound
  1. protected void SearchResultsGridView_OnItemDataBound(object sender, DataGridItemEventArgs e)
  2.      {
  3.          if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
  4.          {
  5.              Label lastModifiedLabel = (Label)e.Item.FindControl("LastModifiedLabel");
  6.              lastModifiedLabel.Text = Convert.ToDateTime(DataBinder.Eval(e.Item.DataItem, "ModifiedDate")).ToString("dd-MMM-yyyy");
  7.              PlaceHolder placeHolder = (PlaceHolder)e.Item.FindControl("RatingPlaceHolder");
  8.              ratingControl = new RatingControl();
  9.              ratingControl.RatingField = (SPField)DataBinder.Eval(e.Item.DataItem, "RatingField");
  10.              ratingControl.ListId = (Guid)DataBinder.Eval(e.Item.DataItem, "ListId");
  11.              ratingControl.ListItemId = Convert.ToInt32(DataBinder.Eval(e.Item.DataItem, "Id"));
  12.              placeHolder.Controls.Add(ratingControl);
  13.          }
  14.     }

Job Done!

I just like to start by saying that this post is not ground breaking. However, when I tried to do this it did take me a while to find the answer, so I thought I’d just blog to add it to the grey matter.

As I’m sure you are all aware you can provision multiple files with preconfigured web parts by using a SharePoint module’s element file and a web part page as a template, by targeting the web part zones on the web part page.

However, trying to connect these caused me a bit of problems. In the end though it was quite straight forward. And can be done by specifying the following attributes in the code snippets and the descriptions given in the table below.

WebPartConnection
  1. <WebPartConnection ConsumerConnectionPointID="MediaSearchConsumer" ConsumerID="MediaResultsWebPart"
  2.                          ProviderConnectionPointID="MediaSearchProvider" ProviderID="MediaSearchWebPart" ID="MediaSearchConnector"/>

Web Part Consumer
  1.       <AllUsersWebPart WebPartZoneID="MiddleZone2" WebPartOrder="0" ID="MediaResultsWebPart">
  2.         <![CDATA[<?xml version="1.0" encoding="utf-8"?>
  3. <webParts>
  4.   <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
  5.     <metaData>
  6.       <type name="Instalec.SharePoint.WebParts.MediaResultsWebPart.MediaResultsWebPart, Instalec.SharePoint.WebParts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9e5b38b443f03fff" />
  7.       <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
  8.     </metaData>
  9.     <data>
  10.       <properties>
  11.         <property name="Title" type="string">Media Search Results</property>
  12.         <property name="Description" type="string">Web part to display the results from the media search</property>
  13.          <property name="Hidden" type="bool">True</property>
  14.           <property name="AllowHide" type="bool">True</property>
  15.       </properties>
  16.     </data>
  17.   </webPart>
  18. </webParts>
  19. ]]>
  20.       </AllUsersWebPart>

Web Part Provider
  1.     <AllUsersWebPart WebPartOrder="1" WebPartZoneID="MiddleZone" ID="MediaSearchWebPart">
  2.             <![CDATA[<webParts>
  3.   <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
  4.     <metaData>
  5.       <type name="Instalec.SharePoint.WebParts.MediaSearchWebPart.MediaSearchWebPart, Instalec.SharePoint.WebParts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9e5b38b443f03fff" />
  6.       <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
  7.     </metaData>
  8.     <data>
  9.       <properties>
  10.         <property name="Title" type="string">Media Search</property>
  11.         <property name="Description" type="string">My Visual WebPart</property>
  12.       </properties>
  13.     </data>
  14.   </webPart>
  15. </webParts>
  16. ]]>
  17.         </AllUsersWebPart>

Connection Consumer Attribute
  1. [ConnectionConsumer("The Search Criteria", "MediaSearchConsumer")]
  2.         public void IMediaSearchConsumer(IMediaSearch mediaSearch)

Connection Provider
  1. [ConnectionProvider("The Search Criteria", "MediaSearchProvider")]
  2.      public IMediaSearch IMediaSearchProvider()
  3.      {
  4.          return this;
  5.      }

 

ConsumerID ID specified in the AllUserWebPart element as seen in the Web Part Consumer above
ConsumerConnectionPointID The ID of the ConnectionConsumerAttribute seen above
ProviderID ID specified in the AllUserWebPart element as seen in the Web Part Provider above
ProviderConnectionPointID The ID of the ConnectionProviderAttribute seen
above
ID This can be any value but will not work without it

Job done… Smile

Okay, before anyone comments on the simplicity of this posting. It merely t allow me to get the script easily in the future.  What’s the purpose of it? It basically retracts a solution and waits until its fully retracted, then deletes it, adds it in again and redeploys it. Not rocket science, but handy. 

#Set up Web Application variable
#Only needed if solution contains Web Application scoped resources

$webApp = "
http://sitetodeployto"

#Set up solution name variable
$solutionName =  "solution.wsp"

#Set up solution file path variable
$filePath = "C:\Deploy51011\" + $solutionName

#Uninstall solution
#Add -WebApplication $webApp if solution contains Web Application scoped resources

Uninstall-SPSolution –Identity $solutionName –WebApplication $webApp  -Confirm:$false

#set up a variable for the solution to allow access to properties
$solution = Get-SPSolution $solutionName

#Wait for solution to be uninstalled
do {Start-Sleep -s 1} while ($solution.Deployed -eq $true)

# add another couple of seconds
Start-Sleep -s 2

#Remove solution from the farm without prompting
Remove-SPSolution $solutionName -Confirm:$false

# add it back in and deploy it
Add-SPSolution –LiteralPath $filePath
Install-SPSolution –Identity $solutionName –WebApplication $webApp –GACDeployment

Hope this saves someone some time Winking smile

After receiving several DCOM error in Event Viewer about permission on various object I decided to amend the  Launch and Activation Permissions of the Key in question. I this case it was IIS Admin Service with the Key {A9E69610-B80D-11D0-B9B9-00A0C922E750} and  IIS WAMREG admin Service with the key {61738644-F196-11D0-9953-00C04FD919C1}. However, when I tried to amend I found that all the properties were disabled as seen below

image

After some Google-ing, I found that there were a couple of registry keys forcing this read-only state.

HKEY_CLASSES_ROOT\AppID\{A9E69610-B80D-11D0-B9B9-00A0C922E750}

HKEY_CLASSES_ROOT\AppID\{61738644-F196-11D0-9953-00C04FD919C1}

To change this – right click on the key and choose advanced, and replace the owner as the administrators group

image

The once this has been done change the administrator’s permissions to full control.

image

Now re-launch the component service and you should now be able to edit the values

I received the above error while trying to install Cisco’s VPN x64 client on my laptop.

 

VPNIntall-error.png

 

After some investigation it turns out this error is caused by default value set by Windows 7 in the registry key

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\MaxNumFilters

By default, this value is set to 8 and apparently can be set to a maximum value of 14. As some of the filters were used by Virtual Box, etc. I decided to set this to the maximum, rebooted and, hey presto, it installed!!

One I’ve been meaning to blog for a while, but just never got round to it Surprised smile

I came across an error when Creating a Custom web service for SharePoint when trying to deploy the web service to the ISAPI  folder, allowing me to access a web service through the  _vti_bin of SharePoint. 

When you create a web service and deploy it to a mapped folder the code behind, as expected goes into the GAC. However, there is no WSDL or DISCO file associated with the web service, so it doesn’t work. 

To create a web service which deploys to the _vti_bin

  1. Add a text file to the {SharePointRoot}\ISAPI mapped folder of your SharePoint project.
  2. Rename your text file to <ServiceName>.asmx. Now you have the service you now need to associate it to the code behind.
  3. Create a class and name it <ServiceName>.cs or .vb if you are that way inclined !!
  4. Add the following attributes to your class
       [WebService(Namespace = "/sharepoint/services/")]’>http://<yourdomain>/sharepoint/services/")]
       [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
       [System.ComponentModel.ToolboxItem(false)]
  5. Next, return to your asmx file created in step 2 and add the following to the top of the file
    <%@ WebService Language="C#" Class="<NameSpaceOfAssembly>.<NameOfServiceClass>, <NameSpaceOfAssembly>, Version=<VersionOfYourAssembly>, Culture=neutral, PublicKeyToken=<PublicKeyOfYourAssembly>" %>
    Note: If you don’t know the public key, build your project, open a Visual Studio command prompt and naviate to your debug folder. Then run the command sn –T <NameOfYourDll>.dll
  6. Next, you need to deploy your solution to get the asmx file into the ISAPI folder and your class into the GAC.
  7. Once deployed, navigate to the web service at <SharePointSite>_vti_bin/<PathIfAny>/<ServiceName>.asmx and you should see your service with any web method you have added.
  8. Next, you will need to create WSDL and DISCO files to allow your to add reference to the service from a project.
  9. For some reason you don’t seem to be able to do this from the ISAPI folder, you receive a file not found error seen below. image
    So,  you need to copy the deployed file from the ISAPI folder to the LAYOUTS folder.
    Then run the command from the Visual Studio command prompt, ran as Administrator

    VisualStudioCommandPrompt

    disco
    /_layouts/.asmx">http://<SiteCollectionUrl>/_layouts/<NameOfService>.asmx 

  10. Once you have successfully ran this for the instructions in the “Generating and Modifying Static Discovery and WSDL Files” section of the Creating a Custom ASP.NET Web Service page on MSDN. 
  11. Copy these newly create wsdl and disco files back into your project and redeploy.

Please note: This tedious process will have to be redone anytime you add a new web method to your service.

Scenario: SSRS, in SharePoint integrated mode, is calling a custom web service located in the SharePoint ISAPI folder access through _vti_bin.  This service is using LINQ 2 SharePoint to query multiple lists in the SharePoint WFE.

A layout of the set up can be seen below. Okay, network diagrams is not my strong point! 

image

Because the authentication is being passed over more that 2 boundaries it looses the user and passes null and the only way to fix this is to use Kerberos.

So here’s how I went about setting it up:

Service Principal Names (SPN) for Service Accounts   

In order pass the Kerberos token you need to set up SPN’s. 

Note: Although I’ve not found confirmation of this SPN’s appear to be case sensitive

setspn.exe -A HTTP/<SSRS_FQDN> SSRSService
setspn.exe –A HTTP/<SSRS_NetBIOSName> SSRSService

Set the <SSRS_FQDN> to the FQDN of the server hosting the SharePoint Integrated SSRS and the <SSRS_NetBIOSName> as the Server name.

These entries can be confirmed by running

setspn.exe –L SSRSService

This should give an output similar to below.

image

The onto the WFE’s

setspn.exe -A HTTP/<SP_WFE_FQDN> SPService
setspn.exe -A HTTP/<SP_WFE_NetBIOSName> SPService

Set the <SP_WFE_FQDN> to either: the name of the server hosting the Sharepoint WFE or if this is an NLB cluster use the cluster name and the same goes for the <SP_WFE_NetBIOSName>

setspn.exe -A MSSQLSvc/<SQL_FQDN>:1433 SqlDbService
setspn.exe -A MSSQLSvc/<SQL_NetBIOSName>:1433 SqlDbService

Set the <SQL_FQDN> to either: the name of the server hosting SQL or if this is a cluster use the cluster name and the same goes for the <SQL_NetBIOSName>

Active Directory Users and Computers

Next, Open Active Directory Users and Computers and change each of the 3 accounts, selecting the Trust this user for delegation to any service (Kerberos Only) option on the delegation tab. 

image

SSRS App Server Changes

On the SharePoint Application Server which is hosting SSRS Open the Local Security Policy and Go to User Management Rights. Change to  “Act as a part of Operating System and “Impersonate a client after authentication” to include the users for both the WFE’s App Pool and SSRS Service Account

image

Report Server Configuration Changes

Open the RsReportServer.config file and locate the <AuthenticationTypes> section. Add <RSWindowsNegotiate/> as the first entry in this section.

Central Admin Changes for Web Application

Next, open Central Admin and Navigate to Application Management –> Manage Web Application

image

Then, select the application, then the authentication providers

image

In the pop-up click on the “Default” link in the Edit Authentication window scroll down to IIS Authentication Settings and choose Negotiate.

image

Scroll Down and Save.

Give this a minute to propagate to the other Servers in the farm and you should now be able to access the Reports which call the web service. 

Came across the above error on all the reports served through SharePoint in integrated mode. Not sure why this happen, but you seem to be able to resolve it by restarting the reports service.

Go to the Reporting Services Configuration Manager ( seen in figure below), connect to the server, then stop and start the service

image

After deploying several event receivers to a variety of lists I found that one of the event receivers did not seem to be working, but instead was firing a old version of the DLL.

So, to check that it was indeed attached, I ran the following PowerShell command.

$spWeb = Get-SPWeb –Identity <SharePointWebUrl>
$spList = $spWeb.Lists["List Display Name"]
$spList.EventReceivers | Select Name, Assembly, Type

After running this I discovered that one of the event receivers had been attached to the list twice! I’m not entirely sure why or how this happened, but my problem was resolved by deleting the duplicate receivers and reattaching it once.

The script is used to delete was:

$spWeb = Get-SPWeb –Identity <SharePointWebUrl>
$spList = $spWeb.Lists["List Display Name"]
$eventsCount = $spList.EventReceivers.Count
$assembly = "Company.SharePoint.Client.EventReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=570c484d87a3aa61"
$class = "Company.SharePoint.Client.EventReceivers.ProvisionServiceAreaSecurityGroups.ProvisionServiceAreaSecurityGroups" $name = "ProvisionServiceAreaSecurityGroupsItemUpdating"
$type = 2
for ($i = 0; $i -lt $eventsCount; $i+=1)
{
     if ($spList.EventReceivers[$i].Assembly -eq $assembly –and
         $spList.EventReceivers[$i].Class -eq $class –and
         $spList.EventReceivers[$i].Type -eq $type –and
         $spList.EventReceivers[$i].Name -eq $Name)
         {
             $spList.EventReceivers[$i].Delete()
         }
}

$spList.Update()

And to add it was:

$spWeb = Get-SPWeb –Identity <SharePointWebUrl>
$spList = $spWeb.Lists["List Display Name"]
$spEventReceiver = $spList.EventReceivers.Add()
$spEventReceiver.Assembly = "Company.SharePoint.Client.EventReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=570c484d87a3aa61"
$spEventReceiver.Class = "Company.SharePoint.Client.EventReceivers.ProvisionServiceAreaSecurityGroups.ProvisionServiceAreaSecurityGroups" $spEventReceiver.Name = "ProvisionServiceAreaSecurityGroupsItemUpdating"
$spEventReceiver.Type = 2
$spEventReceiver.SequenceNumber = 1000
$spEventReceiver.Synchronization = 0
$spEventReceiver.Update()

 

That’s it Winking smile

Bizarre one this: Using SharePoint 2010’s new Modal Popup functionality a link had been created from a tab control to launch the new form for the list items associated with the tab. However, every time the form was submitted, it overwrote one of the existing records in the list. Firstly, I thought I was imagining things !!! But, it turned out that this was caused by using the ID  key in the query string.  I changed ID to RefID all was well.

It makes sense when you know, but it took a while to figure out why this was happening.

Lesson Learned: Don’t every use ID in the query string of a NewForm.aspx