Wednesday, June 4, 2014

Retrieve thumbnail from uploaded video in a Document Library

After a lot of months ignoring SharePointPills I decided to post a new blog entry with an interesting situation that happened in my current project.

The goal was to generate an image from a video used as a thumbnail or splash screen for an external web video player (FlowPlayer in this case, which works really good in almost every single browser). The video was meant to be allocated in a document library that, for external reasons, don't have the Video Content Type. This means that we were not able to access all the nice metadata that SharePoint 2013 generates for you when you upload a video, including a thumbnail for the video located in /[current_path]/[video_name]/Preview%20Images/[video_name].png. So my idea was to copy this video into the OOTB PublishingImages library, which contains the Video Content Type, and then get the thumbnail. Unfortunately using the File.CopyTo method I was able to get only a normal file uploaded into this library. No metadata at all. Even setting specifically the Video Content Type to the item.

After looking for a solution, crawling a thousand blogs and searching in MSDN I found something (a little bit hidden) that could work:  http://msdn.microsoft.com/en-us/library/microsoft.office.documentmanagement.videosets.videoset_members(v=office.15).aspx, and specially this, VideoSet.MigrateVideo: http://msdn.microsoft.com/en-us/library/microsoft.office.documentmanagement.videosets.videoset.migratevideo(v=office.15).aspx

It seems that MigrateVideo generates all the necessary metadata (including thumbnails) for your SPFile video. So I modified my code and the result was something like this:

var videoFile = web.GetFile(itemVideoId);
SPList publishingImagesList = web.GetList("/PublishingImages");

var fileExtension = new FileInfo(videoFile.Name).Extension;
videoFile.CopyTo("/PublishingImages/" + videoFile.Name, true);

var query = new SPQuery();
query.Query = string.Format("{0}", videoFile.Name);
var copiedFiles = publishingImagesList.GetItems(query);
var copiedFile = copiedFiles[0];

// Generate video metadata (thumbnail, etc)
var copiedVideo = VideoSet.MigrateVideo(copiedFile.File);
copiedVideo.SystemUpdate();

Now you can go to your PublishingImages library and see that your video has all the automatic metadata for Video Content Type, including the thumbnail, that you can easily get back using something similar to the code below:

SPFolder thumbnailVideoFolder = web.GetFolder("/PublishingImages/" + copiedVideo.Name + "/Preview Images/");
// Get single thumbnail generated
if (thumbnailVideoFolder.ItemCount > 0)
{
var thumbnailFile = thumbnailVideoFolder.Files[0];
}

As I said, there is not a lot of information about this on the Internet, so I hope this helps someone else.

Cheers!

Sunday, October 14, 2012

SharePoint Server 2013 Preview Setup guide

In this short post I am not going to explain how to set up a SP2013, but it is only to definitely recommend this great installation guide from CriticalPath: http://www.criticalpathtraining.com/_layouts/CriticalPath.Website/DownloadRedir.aspx?type=MemberDownload&id=96

I have just followed this step-by-step guide and apart of the basics (DC + DEV environments, accounts, and so on), it is really helpful to setup the new features, like the App Hosting or the Workflow Manager. Moreover, it includes several powershell scripts to make easier the creation of the AD accounts, disable loopback check and much more. It is also updated regularly.

Maybe it sounds like advertising, but I guess the good things need to have visibility. You need to be registered as member, yet that’s a minor snag. Thanks to Andrew Connell and Ted Pattison for this (and Adis Jugo for sharing).

Sunday, June 3, 2012

Hide Site Collection’s root link in breadcrumb top navigation

Although you can set up a Site Collection so the members of the internal Sites are not able to access the root of the Site Collection, they are still able to have this hateful “You don’t have permissions to this page” SharePoint error. Why grant the access to this page when you can just hide this link in the breadcrumb top navigation?

The goal of this entry is to hide the root navigation item (the root of the Site Collection), so the user is not able to click there, modifying the Site Collection’s master page in SharePoint Designer. I must admit that I don’t like this tool, and my recommendation would be create a new master page, deploy it, and then in the feature receiver set our custom master page as the default one. Anyway, as for the time being, I will use the SPDesigner’s approach.

Here are the steps:

  • Open and modify the current master page using SharePoint Designer or editing it and then uploading to the master page gallery.
  • Locate the control SharePoint:ListSiteMapPath.
  • Add the property ParentLevelsDisplayed as follows ParentLevelsDisplayed="0”

The final control should look like:


runat="server"
SiteMapProviders="SPSiteMapProvider,SPContentMapProvider"
RenderCurrentNodeAsLink="false"
PathSeparator=""
CssClass="s4-breadcrumb"
NodeStyle-CssClass="s4-breadcrumbNode"
CurrentNodeStyle-CssClass="s4-breadcrumbCurrentNode"
RootNodeStyle-CssClass="s4-breadcrumbRootNode"
NodeImageOffsetX=0
NodeImageOffsetY=353
NodeImageWidth=16
NodeImageHeight=16
NodeImageUrl="/_layouts/images/spoverrides/fgimg.png"
HideInteriorRootNodes="true"
SkipLinkText=""
ParentLevelsDisplayed="0"/>

Monday, May 14, 2012

PlanB. event: explain – explore – experience System Center 2012

My company is arranging a System Managament event in Cologne and Stuttgart in a few weeks. Here you will have the chance to glimpse live demos, business cases and a deeper look into the architecture, functionality and design philosophie of System Center 2012. There will be Microsoft and PlanB experts to explain and answer all your questions.

This FREE workshop is in german during the whole day these dates:

  • 24.05.2012 – Stuttgart
  • 11.06.2012 – Cologne

From my point of view this is going to be a unique event about System Center in Germany, so if you are interesed and want to get more info or register, just drop a line or call anke.neumann@plan-b-gmbh.com (+49 7361-55 621-0).

image

Saturday, May 5, 2012

Add Blog and Discusssion Board tabs programmatically in Newsgator Social Sites 2010

Newsgator Social Sites 2010 is a great social add-on for SharePoint 2010 with a lot of features (IMHO maybe too many) and an extensive API to manage programmatically the Activity Stream, the creation of Communities, the Followers of a Community… they have a great support community (https://engage.newsgator.com) where, if you are a partner, you can register yourself to ask the real experts and access special documentation.

In my current project I needed to create a SiteDefintion with several default Newsgator tabs. It seems really easy to go to the Setup page and create a new Blog or Discussion Board using the UI, but to be honest it was really hard to find any kind of documentation about how to do it programmatically (in fact it was impossible).

image

My idea was to provide a SPWebEventReceiver event handler and add in the WebProvisioned event the tabs creation (they have special names, special webparts inside…). Within the following code and guessing what was Newsgator internally doing to add the tabs in the UI (like creating a new “blog” or creating a new “calendar” list), I was able to add them.

/// 
/// A site was provisioned.
///

public override void WebProvisioned(SPWebEventProperties properties)
{
base.WebProvisioned(properties);
SPWeb web = properties.Web;
if (web.WebTemplate.Equals("mySiteDefinition", StringComparison.InvariantCultureIgnoreCase))
{
// Blog
SPWeb blog = web.Webs.Add("news", "News", "News description", 1033, "BLOG#0", false, false);

var tabs = new List();
tabs.Add(new CommunityTab()
{
Id = 0,
CapabilityType = CapabilityType.Overview,
Name = "Home",
Enabled = true,
DisplayType = ServerUI.OverviewTab,
TabIndex = 0
});
tabs.Add(new CommunityTab()
{
Id = 1,
CapabilityType = CapabilityType.List,
Name = "Calendar",
Enabled = true,
DisplayType = "Calendar",
TabIndex = 1,
TemplateType = SPListTemplateType.Events
});
tabs.Add(new CommunityTab()
{
Id = 2,
CapabilityType = CapabilityType.DiscussionBoards,
Name = "Forums",
Enabled = true,
DisplayType = ServerUI.CommunityTabType_DiscussionBoards,
TabIndex = 2,
});
tabs.Add(new CommunityTab()
{
Id = 3,
CapabilityType = CapabilityType.Web,
Name = "News",
Enabled = true,
TabIndex = 3,
DisplayType = ServerUI.CommunityTabType_Blog,
SubWebId = blog.ID
});

SocialGroupPrivacyLevel privacyLevel = SocialGroupPrivacyLevel.Private;

CommunitySetup newsgatorSetup = new CommunitySetup();
newsgatorSetup.SaveSetupConfiguration(web.Site, web, tabs, web.Title, true, privacyLevel, false);

web.Features.Add(CommunityGlobals.WebCommunityFeature, true); // Convert to community before add blog
web.Features.Add(CommunityGlobals.WebScopedSkinningFeature, true); // Skin for NG communities

web.Dispose();
}
}

Anyway, with this code I had 2 serious problems:



  • the blog linked to the “News” tab didn’t have the top Newsgator navigation menu… it seemed like it was an isolated blog, and that was not what I wanted (maybe I needed a NG feature?)
  • the discussions board didn’t show up… and the “boards.aspx” page that it’s deployed using the UI was simply not there… (maybe again I needed a NG feature?)

Because of that lack of documentation, I was “forced” to glimpse the DLLs using ILSpy (great application when you are in trouble) and I realized I needed to change the code a little bit:



  • activate the NewsGator.Communities_NewsGator.Community.Context feature in the blog to enable the top navigation menu.
  • activate the NewsGator.Communities.BoardsPage feature in my site, which deploys the boards.aspx page.
  • add the LinkUrl property in the Discussions Boards page to “boards.aspx

At the end, my successful code looked like:

/// 
/// A site was provisioned.
///

public override void WebProvisioned(SPWebEventProperties properties)
{
base.WebProvisioned(properties);
SPWeb web = properties.Web;
if (web.WebTemplate.Equals("mySiteDefinition", StringComparison.InvariantCultureIgnoreCase))
{
// Blog
SPWeb blog = web.Webs.Add("news", "News", "News description", 1033, "BLOG#0", false, false);

var tabs = new List();
tabs.Add(new CommunityTab()
{
Id = 0,
CapabilityType = CapabilityType.Overview,
Name = "Home",
Enabled = true,
DisplayType = ServerUI.OverviewTab,
TabIndex = 0
});
tabs.Add(new CommunityTab()
{
Id = 1,
CapabilityType = CapabilityType.List,
Name = "Calendar",
Enabled = true,
DisplayType = "Calendar",
TabIndex = 1,
TemplateType = SPListTemplateType.Events
});
tabs.Add(new CommunityTab()
{
Id = 2,
CapabilityType = CapabilityType.DiscussionBoards,
Name = "Forums",
Enabled = true,
DisplayType = ServerUI.CommunityTabType_DiscussionBoards,
TabIndex = 2,
LinkUrl = "boards.aspx"
});
tabs.Add(new CommunityTab()
{
Id = 3,
CapabilityType = CapabilityType.Web,
Name = "News",
Enabled = true,
TabIndex = 3,
DisplayType = ServerUI.CommunityTabType_Blog,
SubWebId = blog.ID
});

SocialGroupPrivacyLevel privacyLevel = SocialGroupPrivacyLevel.Private;

CommunitySetup newsgatorSetup = new CommunitySetup();
newsgatorSetup.SaveSetupConfiguration(web.Site, web, tabs, web.Title, true, privacyLevel, false);

blog.Features.Add(new Guid("b084760c-452e-419d-8639-babb5c0a4283"), true); // NewsGator.Communities_NewsGator.Community.Context feature

web.Features.Add(CommunityGlobals.WebCommunityFeature, true); // Convert to community before add blog
web.Features.Add(CommunityGlobals.WebScopedSkinningFeature, true); // Skin for NG communities
web.Features.Add(new Guid("62ab07ce-3f9f-441a-80ce-b14beae97dd4"), true); // NewsGator.Communities.BoardsPage feature

blog.Dispose();
web.Dispose();
}
}

And it added the wished tabs, working properly.


image


Hope this helps someone!

Friday, January 13, 2012

Attributes of a socially optimized business

Lately I have worked in several projects where I had to develop some kind of social plugins for SharePoint, or integrate any of the available products to “socialize” SharePoint, or identify the real needs of the client and the better solution for them. It seems that more and more companies realize that a good relationship between their co-workers means better performance and better results.

Anyway, I still find a lot of managers (especially in Germany) that are not able to see the benefits of this approach. They are stuck in the e-mail age. They think that this is just a waste of time and of course, a waste of money –as these solutions are not especially cheap-.

For those people, I found this nice infographic signed by Dachis Group. There are a lot of new/old ideas… I especially like the massive shift from “me” to “we” sentence and the impact on the global workforce.


Saturday, December 31, 2011

Modifying Leading and Trailing HTML in SPLongOperation

Long running operations show those nice screens in SharePoint telling the user that something that takes a lot of time is being executed and they have to be patient. These screens can be customized with such a title and a subtitle (LeadingHTML and TrailingHTML) which can be set only once for each operation. But what if I have several steps inside the same long operation, and I want to modify these texts and show the user each of the steps?

I took the idea of my solution from James Boman’s blog, and with a very few modifications I got it working for SP2010. The goal is to create a new custom SPLongOperationEx class that will modify directly the HTML code of the long running operation screen via Javascript. This will override the important methods described in the MSDN page, so we can keep the functionality in our new class.

In the constructor, we add the following javascript code to modifiy in the client-side the text.

<script language='javascript'> 
    var spnTarget = document.getElementById('[Leading or TrailingHTML]')
    spnTarget.innerHTML = \"[Text to change]"\
</script>

Probably you will have now the idea of what you have to do, right? The new class will look then as follows:


public class SPLongOperationEx : IDisposable
{
#region Storage

private SPLongOperation mobjSPLongOperation;
private System.Web.UI.Page mobjPage;
private bool mblnBegun = false;
private string mstrChangeScript = "";
private bool disposedValue = false;
#endregion

#region Properties

public string LeadingHTML
{
get
{
return mobjSPLongOperation.LeadingHTML;
}
set
{
mobjSPLongOperation.LeadingHTML = "" + value + "";
if (mblnBegun)
{
mobjPage.Response.Write(string.Format(mstrChangeScript, "spnLeading", value));
mobjPage.Response.Flush();
}
}
}

public string TrailingHTML
{
get
{
return mobjSPLongOperation.TrailingHTML;
}
set
{
mobjSPLongOperation.TrailingHTML = "" + value + "";
if (mblnBegun)
{
mobjPage.Response.Write(String.Format(mstrChangeScript, "spnTrailing", value));
mobjPage.Response.Flush();
}
}
}

#endregion

#region Constructor

public SPLongOperationEx(System.Web.UI.Page page)
{
mobjPage = page;
mobjSPLongOperation = new SPLongOperation(page);
}

#endregion

#region Public Members

public void Begin()
{
mblnBegun = true;
mobjSPLongOperation.Begin();
}

public void End(string vstrRedirectPage)
{
mobjSPLongOperation.End(vstrRedirectPage);
}

public void End(string vstrProposedRedirect, Microsoft.SharePoint.Utilities.SPRedirectFlags rgfRedirect, System.Web.HttpContext context, string queryString)
{
mobjSPLongOperation.End(vstrProposedRedirect, rgfRedirect, context, queryString);
}

#endregion

#region IDisposable

protected void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
this.mobjSPLongOperation.Dispose();
}
}
this.disposedValue = true;
}

void IDisposable.Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

#endregion
}

NOTE: Although it works, I am still having the issue that it is required to duplicate the calling to the setter, because if the new text is set only once, it does not work properly. I guess it is something related to the life-cycle between server and client side… tough stuff anyway.

Tuesday, November 15, 2011

Configure People Search in SharePoint 2010 using https

I have made this configuration a lot of times, but last week I had problem that I could  solved thanks to this blog.

To configure People Search in SP2010, we all know that once you have your one Enterprise Search Site set up, you need to configure the scope and add the MySite url address to the crawling configuration. So go to CA –> General Application Settings –> Farm Search Administration –> Search Service Administration –> Content Sources and select your content source (normally Local Sharepoint sites). Here you need to add the MySite web application to the Start Addresses, but using the sps3 protocol, not the http.

image

I did realize that my User Profile application was running under HTTPS, but I thought that the sps3 protocol should work anyway. Well, that is NOT correct. If your User Profile web application uses HTTPS protocol, you need to add the sps3s to the Start Addresses configuration.

After that, check that the crawling account has access to the User Profile store checking the option in the Administrators configuration of the User Profile Service.

image

The People Search should work now.

Monday, October 31, 2011

Send a SharePoint 2010 document to an external web service using Records Center (II)

In the previous entry I explained the solution adopted to send documents from a SharePoint library to an external web service using Records Center, and the first of the 2 steps to do that:

  • Add a a new connection in the configuration in Central Admin –> General Application Settings –> Configure send to connections.
  • Create a new webservice in a different Web Application (normal ASP.NET service).

In this new post I will explain how to override the standard service OfficialFile.asmx to copy documents to the Records Center, with new functionality to send to our own external service. Following one of the steps that Wictor Wilén described in this post, I managed to implement this same MOSS web service.

First of all, create a new VS2010 project of type ASP.NET Web Service application.

image

Then, using the tool wsdl.exe from the .NET Framework SDK we can get an interface based on the WSDL definition of the OfficialFile.asmx service. So to ensure that we have the latest web service interface, let’s execute the following command to get the interface we will add to our code.

wsdl /out:IRecordsRepositorySoap.cs /n:TestRecordsCenter_WebRole /serverinterface 
http://<serverUrl>/<recordsCenterUrl>/_vti_bin/OfficialFile.asmx?WSDL

That command will create the file IRecordsRepositorySoap.cs containing the definition of the web service. Someting with the following structure:


/// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
    [System.Web.Services.WebServiceBindingAttribute(Name="RecordsRepositorySoap", Namespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/")]
    public interface IRecordsRepositorySoap {
       
        /// <remarks/>
        [System.Web.Services.WebMethodAttribute()]
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://schemas.microsoft.com/sharepoint/soap/recordsrepository/SubmitFile", RequestNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", ResponseNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        string SubmitFile([System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] byte[] fileToSubmit, [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] RecordsRepositoryProperty[] properties, string recordRouting, string sourceUrl, string userName);
       
        /// <remarks/>
        [System.Web.Services.WebMethodAttribute()]
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://schemas.microsoft.com/sharepoint/soap/recordsrepository/GetFinalRoutingDes" +
            "tinationFolderUrl", RequestNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", ResponseNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        DocumentRoutingResult GetFinalRoutingDestinationFolderUrl([System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] RecordsRepositoryProperty[] properties, string contentTypeName, string originalSaveLocation);
       
        /// <remarks/>
        [System.Web.Services.WebMethodAttribute()]
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://schemas.microsoft.com/sharepoint/soap/recordsrepository/GetServerInfo", RequestNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", ResponseNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        string GetServerInfo();
       
        /// <remarks/>
        [System.Web.Services.WebMethodAttribute()]
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://schemas.microsoft.com/sharepoint/soap/recordsrepository/GetRecordRoutingCo" +
            "llection", RequestNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", ResponseNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        string GetRecordRoutingCollection();
       
        /// <remarks/>
        [System.Web.Services.WebMethodAttribute()]
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://schemas.microsoft.com/sharepoint/soap/recordsrepository/GetRecordRouting", RequestNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", ResponseNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        string GetRecordRouting(string recordRouting);
    }

Now we can use this interface as a Service Reference, so add this file directly to the solution to implement at least these five methods. Then create a new service .asmx (it is not necessary to call it OfficialFile.asmx) and the final solution will look to something like this:


image


Keep in mind that it is necessary to implement and override the five methods of the Service reference. One important point is that these methods can return anything (“hello World”, i.e., but obviously the connection will not work properly.´), except the method GetServerInfo(), which MUST return a string containing an XML string with the following format:


<ServerInfo><ServerType>Test Server</ServerType><ServerVersion>1.0</ServerVersion></ServerInfo>

Where ServerType and ServerInfo can be anything. No official documentation was found with this info, and I could only find something about that in Wictor’s blog.


The OfficialFile.asmx file will finally look like this (there are a lot of discussions in different forums about the correct way to declare this service, but this should work 100%). Keep in mind that you may need to deploy the built DLL to the GAC.


<%@ WebService Language="C#" Class="OfficialFile, SharePointToHYDMediaService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f16d3448d010e7e6" %>

And the code-behind (except using sentences):


[WebService(Namespace = "http://schemas.microsoft.com/sharepoint/soap/recordsrepository/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class OfficialFile : System.Web.Services.WebService, CustomRecordsCenter_WebRole.IRecordsRepositorySoap
{
   
    public string SubmitFile(byte[] fileToSubmit, RecordsRepositoryProperty[] properties, string recordRouting, string sourceUrl, string userName)
    {
        return "<ResultCode>Hello World</ResultCode>";
    }
    public string GetServerInfo()
    {
        return "<ServerInfo><ServerType>Test Server</ServerType><ServerVersion>1.0</ServerVersion></ServerInfo>";
    }
    public string GetRecordRoutingCollection()
    {
        return "Hello World";
    }
    public string GetRecordRouting(string recordRouting)
    {
        return "Hello World";
    }
    public DocumentRoutingResult GetFinalRoutingDestinationFolderUrl(RecordsRepositoryProperty[] properties, string contentTypeName, string originalSaveLocation)
    {
        return null;
    }
}

The next step consists on deploy the webservice to a different web application that you can create directly in the IIS Manager. This application needs to run under the SharePoint application pool to work properly, so configure this when you create the WebSite clicking the button Select application pool.


image 


Publish your service to the web application, and cross fingers.


image


If everything went fine, you should be able to browse your custom web service.


image


Finally, add the new service (that remember is based on the original OfficialFile.asmx) as a Service Connection as described in the part I of this post. Clicking in the link to test the service, should show a confirmation popup.


image


The custom Service Connection to send documents to an external web service is up and running, and although making a real test will throw an exception (basically because all the “return ‘Hello World’”) a debugger can be attached to the w3wp.exe process to check that it is working with the custom web service.

Monday, October 17, 2011

Send a SharePoint 2010 document to an external web service using Records Center (I)

One of the main demands that a SharePoint customer -that has just jumped into the platform- usually makes is “how can I connect SharePoint to send/receive documents from SAP/Documentum/MyOwnRepository/whatever?”.

To receive documents or just any kind of data with associated metadata and, i.e., upload these items to a List, I would say that one of the best options is to use standard Web Services. If the standard services are not enough, then we can create our own service and then use the Object Model to build that extra-functionality.

To send documents (again, with or without associated metadata), the chosen solution in out last customer was use some Records Management functionality as an intermediate step to send the document to the customer’s system. The Records Center is an Enterprise site template available since SharePoint 2007 which functionality has been slightly modified for 2010. The cool point of this solution is that the document can be sent to the Records Center either using the contextual menu (Send To –> “Name of the external connection”), or with a custom Activity in a workflow (to send batches of documents)

These are the steps to implement this solution:

  • Add a a new connection in the configuration in Central Admin –> General Application Settings –> Configure send to connections.
  • Create a new webservice in a different Web Application (normal ASP.NET service). This will hold the bridge connection between SharePoint and the external service.

So far, it seems a simple and fast to implement solution, but there are a couple of “special issues features” that may give you headaches for days.

In this first post I am going to cover the connection configuration step and just take a look around to the Records Center, to understand how it works, and what are we going to do.

Let’s begin going to Central Admin –> General Application Settings –> Configure send to connections and add a new connection. You will need to use the service OfficialFile.asmx, as this service implements the methods to copy files into the Records Center. You can find thousands of resources about this service, but the official specification is here: http://download.microsoft.com/download/8/5/8/858F2155-D48D-4C68-9205-29460FD7698F/[MS-OFFICIALFILE].pdf

To set up a new connection using this service, the URL should have the format http://<server>/<recordsCenterUrl>/_vti_bin/officialfile.asmx. Notice that you should have created a Records Center site in order to use this functionality.

image

Now you should be able to see a new link in the contextual menu of the documents libraries. This link wil execute the action specified in the connection settings (copy to Records Center, i.e).

image

Click the “Official File” link in the contextual menu will copy the document to the Records Center using the service OfficialFile.asmx, and that is exactly what we want to do with our own service. Now that we –barely- know how it is going to work, we can create our own service based in the OfficialFile.asmx, and override it to provide a connection to the external service. I personally found this solution great because it is going to use a connection to an external system inside SharePoint context. My point: we are gonna have everything under control.

In the next post I will implement the second part of the solution. That is, develope a “Hello World” web service and give a couple of useful tips to make it work.

Thursday, September 29, 2011

SharePoint Timer Job stops at “Initialized” and clear SharePoint Cache

There are a lot of configuration items that SharePoint cache by default: features, solutions and timer jobs among many other stuff. According to Joe Rodgers in this entry, “The config cache is where we cache configuration information (stored in the config database) on each server in the farm. Caching the data on each server prevents us from having to make SQL calls to pull this information from the configuration database. Sometime this data can become corrupted and needs to be cleared out and rebuilt.”. Yeah.

So just imagine, what could happen if you deploy a solution with a custom timer job, and you forgot in the first line of code something like

System.Diagnostics.Debugger.Launch();


Boahhh: no trace, no error, no action, nothing.


Then you will probably need to clear the SharePoint configuration cache (like Joe Rodgers explain) or at least, if you know what is causing your problem, re-install the feature of your TimerJob. That will clear the cache for that feature/timerjob and when you upgrade your solution and install the feature again, SharePoint will cache the right one.

Tuesday, September 13, 2011

Check if a SharePoint user is member of an AD group

There are several ways to get this information:

But the easiest way I found was using the System.DirectoryServices.AccountManagement namespace. Incredible short implementation, best results… so something like this will solve the problem.


using System.DirectoryServices.AccountManagement;

protected bool CurrentUserIsMemberOfGroup(string groupName)
{
string userLogin = SPContext.Current.Web.CurrentUser.LoginName;
// To get the right context, run with elevated privileges
SPSecurity.RunWithElevatedPrivileges(delegate()
{
var principalContext = new PrincipalContext(ContextType.Domain);
var userPrincipal = UserPrincipal.FindByIdentity(principalContext, System.DirectoryServices.AccountManagement.IdentityType.SamAccountName, userLogin);
var group = GroupPrincipal.FindByIdentity(principalContext , groupName);
return userPrincipal.IsMemberOf(group);
});
}

Notice the SPSecurity.RunWithElevatedPrivileges, as it is necessary to get the info from our AD (in case it is not located in the same machine as our beloved SharePoint). Otherwise, you won’t get access to the “ContextType.Domain”.


Hope this helps somebody.


Cheers!

Sunday, September 4, 2011

Custom MySites TopLinkBar placed in a not-MySites site

It is hard to describe in only one line what I have been doing last week. Sorry about that, but I guess the problem is quite common between customers that want to integrate several different kind of SharePoint 2010 templates with the nice look & feel that MySites offers.

In short, I was asked to keep the same TopLinkBar from MySites in an intranet Team site and in a Basic Search Center site. They 3 were running obviously in 3 different web applications, and they should use the same links.

image

My first idea: add the control

<SharePoint:DelegateControl runat="server" ControlId="GlobalNavigation"/>

to the v4.master (for the TeamSite) and the minimal.master (for the SearchCenter) pages. Then just modify through the SiteSettings the Top Link Bar links in each of the Site Collections. The problem is that this kind of configuration is specifically for MySite based templates, and this will not work in any other type of templates. So although you can see now the TopLinkBar in your site, it is in fact a useless dummy bar that contains links to nowhere…


My second idea: create an own TopLinkBar, using a copy of the User Control TopNavBar.ascx located in CONTROLTEMPLATES which is the control rendered when you place the


<SharePoint:DelegateControl runat="server" ControlId="GlobalNavigation"/>

in the master pages. We can set this new control to our solution in a Module, adding this to the elements.xml


<Control Id="GlobalNavigation" Sequence="10" ControlSrc="~/_CONTROLTEMPLATES/CustomTemplates/CustomTopNavigation.ascx" />

Then, inherit from the class MySiteDataSource, which is being used to set the links in the navigation bar, and modify programmatically the navigation to set my own links. Here again there is a problem, which is the sentence in the MSDN article: This class and its members are reserved for internal use and are not intended to be used in your code. So no, I could not access these methods…


My third and final idea: it is pretty much the second one (create an own CustomTopNavigation.ascx control, add it to the master pages, blablabla) BUT, create my own SiteMapDataSource in my CustomTopNavigation control that I will set to the TopNavigationMenu itself in the DataSourceID parameter.


<SharePoint:AspMenu
     ID="MySiteTopNavigationMenu"
     Runat="server"
     EnableViewState="false"
     DataSourceID="MySiteTopNavDS"
     AccessKey="<%$Resources:wss,navigation_accesskey%>"
     UseSimpleRendering="true"
     UseSeparateCss="false"
     Orientation="Horizontal"
     StaticDisplayLevels="1"
     MaximumDynamicDisplayLevels="1"
     PopOutImageUrl=""
     SkipLinkText=""
     CssClass="s4-mysitetn">
  </SharePoint:AspMenu>
  <asp:SiteMapDataSource runat="server" id="MySiteTopNavDS"  SiteMapProvider="MySiteMapProvider" ShowStartingNode="false" />

Then, create somewhere in the code a custom SiteMap provider and set my links.


public class CustomNavigation : PortalSiteMapProvider
    {
        public override SiteMapNodeCollection GetChildNodes(System.Web.SiteMapNode node)
        {
            PortalSiteMapNode pNode = node as PortalSiteMapNode;
            if (pNode != null)
            {
                if (pNode.Type == NodeTypes.Area)
                {
                    SiteMapNodeCollection nodeColl = base.GetChildNodes(pNode);
                            SiteMapNode childNode = new SiteMapNode(…, "My Newsfeed");
                            SiteMapNode childNode1 = new SiteMapNode(..., "My Content");
                            SiteMapNode childNode2 = new SiteMapNode(…, "My Profile");
                            SiteMapNode childNode3 = new SiteMapNode(…, "New Link");
                            nodeColl.Add(childNode);
                            nodeColl.Add(childNode1);
                            nodeColl.Add(childNode2);
                            nodeColl.Add(childNode3);

                    return nodeColl;
                }
                else
                    return base.GetChildNodes(pNode);
            }
            else
                return new SiteMapNodeCollection();
        }
    }


And then, finally, modify in the web.config files of the web applications we want to change (not for MySites web application, obviously) the entry for the SiteMap provider. That means, replace


<add name="MySiteMapProvider" description="MySite provider that returns areas and based on the current user context" type="Microsoft.SharePoint.Portal.MySiteMapProvider, Microsoft.SharePoint.Portal, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />

with


<add name="MySiteMapProvider" type="[namespace].CustomNavigation, […]" NavigationType="Global" /> 

I know it is not the cleanest way to modify this TopLinkBar, so if anyone has made it with another (and easier) method, I would really apprecciate this info Smiley.


Cheers!

Sunday, June 19, 2011

Show webpart custom property (PersonalizationScope.User) for Contribute users

Weeks ago in a project I added several custom webparts with one or more of the following custom properties:

[Category("Guisu properties")]
[Personalizable(PersonalizationScope.User)]
[WebDisplayName("Show Company")]
[WebBrowsable(true)]
public bool ShowCompany
{
get
{
return showCompany;
}
set
{
showCompany = value;
}
}


My idea was to have different levels of accessing these properties, so the administrator could see ALL the properties, including those with PersonalizationScope.Shared, and the users with Contribute permissions could only see the properties with PersonalizationScope.User. Moreover, according to MSDN, these properties should be user-specific.

Cool, huh? Well, it is not so easy. Thanks to this entry in http://akifkamalsyed.wordpress.com/ blog, I realized it is necessary to change several SafeControl entries.

This could be a problem for deployments... if we did not have the safe control entries configuration in VS2010 :) So the normal SafeControl properties for a default webpart are shown like this:




Just change them to the following configuration, redeploy, and it should work:

Thursday, June 9, 2011

New city, new country, new job...

Hi all,

So yes, it is true. I just left my job and my friends in BCN to move to Germany. That's why I have not been able to update the blog so often as I wanted (sorry to the 2 guys who have asked things about the Facebook API ;)).

Anyway, in the following days I will start to post again about SharePoint and (I hope) interesting technology stuff in my pseudo-English. Maybe I will use the blog to post funny things about Germany as well.

To sum up: I work right now for a small (but full of SharePoint experts) company called PlanB. There are a lot of people here that are very VERY good, so take a look to their blogs (some of them are in English and not in German :P).

Of course I would like to thank my previous workmates of Spenta for all the fun we had working together. It was a great time guys. I know the office will not be same after me, but you know, you will have to keep on living :D

That was enough for now... I have already a couple of posts in mind, so stay tuned.

Auf die Plätze, fertig, los!

Monday, February 28, 2011

Examinar logs en SharePoint

Soy un tipo duro. Abro el notepad y empiezo a programar. O eso, o te monto un powerpoint. ¿Cómo no me va a gustar examinar los logs ULS de SharePoint a pelo? Con sus milisegundos, su criticidad, y su incomprensibles callstacks…

En fin, basta de ironías. Todos los que nos dedicamos a ello hemos sufrido con las trazas de SharePoint, y conozco poca gente que utilice un visor de logs. Mi recomendación: usadlo. El que sea, hay varios buenos, bonitos y baratos (libres, de hecho). Os cuento una historia que me ocurrió hace un par de semanas en un cliente.

No se podían abrir los archivos .xlsx en el visor de Excel de SharePoint 2010. El visor arrancaba pero teníamos el error “Se ha producido un error. Inténtelo de nuevo”. Revisados todos los servicios, no se me ocurría ninguna idea, y en los logs de SharePoint no había visto nada, así que usé los foros de msdn en mi beneficio y rápidamente 3 MVPs de España me contestaron con distintas ideas. Una de ellas era usar el ULS Viewer o el SharePoint Log Reader. De manera excéptica hice caso y en cuanto abrí el fichero con el ULS Viewer –BAM!-, notificación crítica: “Start Excel calculation services”. Lo reviso otra vez, y efectivamente, estaba parado. Arranco, pruebo, y funciona a la primera.

Moraleja: las aplicaciones tiene logs por algo, revísalos, y si son tan retorcidos como los de nuestro bien amado SharePoint, utiliza ayudas.

Wednesday, February 9, 2011

Máquina virtual VMWare a partir de VHD de Hyper-V x64

 

Ayer me he tenido que pelear con máquina virtuales, y aunque no tenga que ver ni con SharePoint ni con desarrollos en .NET, me parece interesante tener anotado todo mi curro en algún lado.

La situación es la siguiente: tengo un VHD de una máquina virtual de 64 bits realmente interesante para mí <autobombo>como puede ser la de CSP</autobombo> en Hyper-V. No tengo Hyper-V en mi máquina, y no tengo posibilidad de instalarlo. Sólo tengo un Virtual PC 2007 en un Windows 7 Professional x64, así que intento crear una nueva máquina virtual VPC a partir de ese VHD. No arranca, y es que no recordaba la incompatibilidad Hyper-V x64 con Virtual PC o Virtual Server de la que hablan en este post.

Buscando por google, lo más cerca a lo que me acerqué fue este otro post, en el que borra los Integration Services y se carga la HAL. No es mi caso, porque no tengo un Hyper-V a mano Sad smile

Los que me conocen un poquito saben de sobra que soy un fan de VMWare Player (free y en inglés), así que buscando entre los posts del foro de VMWare llegué a la siguiente solución:

  1. Crear la máquina virtual en Virtual PC 2007 a partir del VHD de Hyper-V (sí, ya sé que no arrancará, pero esa no es mi intención) y crear un .vpc.
  2. Descargarse el VMWare vCenter Converter (versión de evaluación) para convertir ese .vpc en un archivo que reconozca VMWare, y que, con suerte, pueda arrancar. Descargarse tmabién el VMWare Player, si es que todavía no lo tenéis.
  3. Convertir el vpc en un archivo vmx poniendo atención en el tamaño del disco (que sea el mismo que nuestro original) y en las distintas opciones que nos ofrece.
  4. Arrancar la nueva máquina en VMWare y descargarse el software que te solicita al principio (es uno de esos popups con muchas letras y un botón de aceptar, y yo soy un dedo izquierdo rápido, jeje).

La transformación habrá creado una lista de archivos, entre los cuales estarán el archivo de configuración de VMWare y el disco transformado en formato .vmdk. La máquina arrancará ahora como la seda Smile

 

Friday, February 4, 2011

Para empezar en SharePoint 2010

Hace 2 días tuve la suerte de poder pasarme por Madrid y asistir a un evento / mesa redonda / whatever de SharePoint 2010 con 4 MVPs de SharePoint (http://david-martos.blogspot.com/2011/01/desarrollo-en-sharepoint-para.html). En realidad era una charla más bien dirigida a desarrolladores .NET que quisieran introducirse en el mundillo. El caso es que salieron varios tipos de preguntas que son obvias para todo desarrollador de SharePoint, pero no lo son tanto para otros mortales :-)

Para los que quieran saber si realmente SharePoint les servirá para sus intereses y no sepan lo que se van a encontrar cuando se pongan manos a la obra, ahí va un remueve-conciencias:

  • ¿Qué me aporta SharePoint en comparación con un portal ASP.NET?
  • ¿Qué es el concepto de lista en SharePoint?
  • ¿Puedo manejar alegremente las bases de datos de SharePoint?
  • ¿Puedo modificar los estilos / master pages / xsl de los elementos de SharePoint?
  • ¿Qué objetos out-of-the-box me ofrece SharePoint?
  • ¿Cómo se implementan búsquedas en SharePoint?

En esta charla también me dí cuenta de que muchos desarrolladores de SharePoint nos especializamos en una de todas las áreas que ofrece SharePoint y dejamos otras a un lado ("he venido a hablar de mi libro, oiga"). En este sentido, para los que empiezan, y para los que tienen olvidadas ciertas funcionalidades de SharePoint 2010, he encontrado este minicursillo en donde podréis ver ejemplos estilo "Get Started" con todo lo que ofrece: http://msdn.microsoft.com/en-us/sharepoint/ee513147

Espero que os sirva de ayuda.

Friday, November 26, 2010

Nuevos blogs y productos de Spenta


Este no es un post técnico, pero sí que es muy interesante para mí porque me toca de cerca.


Empiezo haciendo un poco de publicidad a dos blogs.


  • El primero de ellos de uno de un crack con muchísima experiencia en SharePoint y que por fin se ha animado a compartir su sabiduría con el mundo: sharejoint.blogspot.com. El propio título lo dice todo... comparte tu... ejem... joint :-)

  • El segundo es de una visionaria, una diseñadora con mayúsculas que convierte una página web en un recital para la vista: bymadeinmind.blogspot.com. Ella es en gran parte la responsable del nuevo producto de la casa en la que trabajo...



StreetCare, de Spenta. Es el nuevo producto de crowdsourcing (cómo mola el palabro) para el report de incidencias en un ayuntamiento por parte de los propios ciudadanos. Echadle un vistazo al vídeo y a la página web, porque no tiene desperdicio.

Espero postear durante el finde algo más técnico.

Tuesday, November 16, 2010

Modal popup de jQuery con EventHandling de Repeater en codebehind

Siguiendo con los posts acerca de jQuery, espero que este os solucione la papeleta más de una vez. Muchos leeréis el título y os preguntaréis para qué necesito manejar los eventos en el codebehind, si utilizo jQuery y ésta es una tecnología diseñada para correr en cliente. De hecho, muchos listillos expertos dan esa respuesta en muchos foros.

Mi situación es la siguiente: tengo un Repeater ASP.NET, y en cada uno de ellos un botón que abre un modal dialog de jQuery como el de mi anterior post. Dentro de ese popup existe un link que debe ser construido irremediablemente en código en servidor, y que evidentemente, será distinto para cada uno de mis popups (recordemos que vienen de botones distintos del Repeater).

La solución que aquí planteo seguramente no sea la mejor, pero era la que me permitía mantener la gestión de eventos que ya tenía en los botones del Repeater utilizando el atributo CommandName de asp:Button. Lo que haré será registrar la llamada a un nuevo método en el evento ItemCommand del Repeater, de la siguiente manera:


private void RptDocumentInstancesListItemCommand(object source, RepeaterCommandEventArgs e)
{
try
{
if (e.CommandName == "Ver")
{
...
Page.ClientScript.RegisterStartupScript(Page.GetType(), "openPopupDialogVer", string.Format("", idDocGuid)).Value));
...
}
}
}


Y modificar el script que abre el modal popup en jQuery, pasándole por parámetro el valor que he tenido que calcular en el codebehind (que en mi caso es un identificador de un documento) y asignándoselo, si quiero, a otro a otro input del formulario.


function openPopupDialogVer(docGuid) {
$(function () {
$('input[id^="idDoc"]').val(docGuid);
$("#panelVer").dialog('open');
return false;
});
}


Así conseguimos que al pasar por el evento ItemCommand, se llame a la función javascript que tiene que llamar jQuery (nuestro .dialog('open')).

Estoy listo para las críticas :-)