Showing posts with label sharepoint. Show all posts
Showing posts with label sharepoint. Show all posts

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"/>

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:

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.

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.

Wednesday, November 10, 2010

Modal Popup de jQuery en MOSS 2007 con EventHandling de botones ASP.NET

Desde hace un par de semanas he dejado a un lado SharePoint 2010 y he estado retocando un viejo proyecto de SharePoint 2007. Entre otras muchas cosas, me ha tocado hacer frente a la configuración del AjaxControlToolkit en un portal de publicación, algo que ya había hecho hacía un año sin problemas. Con esto quería conseguir cosas como los ModalPopupExtender que tanto gustan a los UX designers.

Pues bien, será que me he vuelto más viejo, más tonto, o ambas, porque aún siguiendo el manual "straightforward" de MS, no logré hacer que funcionara (claro, que también puede ser la cantidad ingente de modificaciones que hay que hacer al web.config... grrr).

Hablando con Luru del problema, me acabó convenciendo de que jQuery es mucho mejor, y de que además "es el futuro, maaacho". Sinceramente mis recuerdos de hace un año sobre jQuery eran desastrosos, por eso elegí AJAX en su día. El caso es que la desesperación hizo que me bajara los .js de jQueryUI y una CSS que me gustó de su página, con la intención de conseguir algo parecido a esto.

Mmmhhh... no tenía mala pinta, pero ¿funcionaría? Meto los .js y los CSS en el LAYOUTS de la carpeta 14 12 y los añado en la master page, haciendo referencia directa a la ruta "_layouts/jquery-ui-1.8.6.custom.js" y a la de jquery. También incluyo el siguiente código en mi User Control con el panel que quiero mostrar en el popup.



$(document).ready(function () {
$("#panelAdjuntar").dialog(
{
autoOpen: false,
modal: true
});

$("#btAdjuntar").click(function (event) {
$("#panelAdjuntar").dialog('open');
});
});




En donde btAdjuntar es un botón en cliente normal, de los de toda la vida:






Y el panelAdjuntar es el div que quiero mostrar en el modal popup. Este div incluye varios controles ASP.NET como Label, Button, TextBox, FileUploads, y sus respectivos EventHandler.



...



Pruebo todo esto y voilá. Funciona! Juhuuu! Muestra el modal popup y además es bonito :-)

Le doy a uno de los botones ASP.NET de dentro de nuestro div panelAdjuntar y........... me c$#@-kW%a!!! No funciona nada. Depuro el código y tampoco. Vamos, que no hace nada, que no pilla el click del botón :-(

Un par de minutos más tarde encontré la solución aquí. Al parecer hay que modificar el código moviendo el div al form, y que así pille los eventos. El script me queda así entonces.





Y ahora sí que funciona el modal popup. Con sus botones ASP.NET y todo. Y además sigue siendo chulo :-)

Y yo... definitivamente me paso a jQuery. Gracias Luru!


PS: ¿Y vosotros? ¿jQuery o Ajax?.......


Friday, October 15, 2010

Configuración de PivotViewer con error 'Element is already the child of another element'

PivotViewer es un control Silverlight para manejar cantidades considerables de datos de una manera gráfica. Parte de la aplicación de Microsoft Labs Pivot, y de los muchos ejemplos de apps que puedes encontrar en internet, confieso que estoy especialmente enamorado de este de la copa de fútbol. En ese enlace, además de pasar las horas muertas con las estadísticas del mundial, os podréis hacer una idea del potencial de la herramienta, y en la web oficial de Pivot y del control PivotViewer podréis aprender un poquito más: www.getpivot.com, www.silverlight.net/learn/pivotviewer/

El caso es que trabajando diariamente con SharePoint 2010, lo primero que viene a la mente de los de arriba es cómo se puede meter esto dentro de un webpart de Silverlight. No es que sea para nada complicado, y más siguiendo paso a paso este post de Tim Heuer. Genial. Te encuentras en la situación en la que lo tienes todo, compila, 0 errores, 0 warnings, F5, se abre la webapp de prueba, vamosvamosvamos... zas: exception at InitializeComponent() - Element is already the child of another element. Buscas en google y te desesperas, porque es uno de los errores más genéricos de Silverlight.

Si revisas tu código te das cuenta de que más simple no puede ser, que tienes la última referencia a System.Windows.Pivot, y que ese error no te va a decir dónde falla tu código... porque tu código simplemente no falla. Lo que pasa es que la configuración de tu servidor no es la correcta. ¿Has podido olvidar instalar alguna de las 'n' tools-sdk-kits-update-whatever que leíste en los pre-requisitos? Aquí viene el listado completo (funcionando para Windows Server 2008 R2, con VS2010 RTM y Silverlight 4, y actualizado a fecha del post):
Sé paciente, las dos primeras tardan. La última instala la SDK en C:\Program Files (x86)\Microsoft SDKs\Silverlight\v4.0\Toolkit\[version], así que revisa que es la DLL que usas al añadirla como referencia en tu proyecto.