Tuesday, February 16, 2010

Remove web part from web part gallery

Sometimes you need to prevent users to add your web part to pages. You can do this simple when deactivate a feature. Removing the web part is based on assembly name.

public override void FeatureDeactivating(SPFeatureReceiverProperties properties) 
{
string assemblyName;
assemblyName = System.Reflection.Assembly.GetExecutingAssembly().FullName;

using (SPSite site = (SPSite)properties.Feature.Parent)
{
using (SPWeb web = (SPWeb)site.OpenWeb())
{
if (web.CurrentUser.IsSiteAdmin)
{
List<int> toDelete = new List<int>();
SPList list = site.GetCatalog(SPListTemplateType.WebPartCatalog);

foreach (SPListItem item in list.Items)
{
if (item["WebPartAssembly"].ToString() == assemblyName)
{
toDelete.Add(item.ID);
break;
}
}

foreach (int i in toDelete)
{
SPListItem item = list.GetItemById(i);
item.Delete();
}

list.Update();
}
}
}
}



Monday, February 8, 2010

Get or set User Profile property using web services

A way of accessing User Profile properties is trough UserProfileManager. If you are an administrator you can read/update/create properties. If you are not admin then you are in trouble. You cannot read or update properties even running with elevated privileges.

A solution is to give all authenticated users the right to edit their profiles:

  • Navigate to Central Admin
  • Navigate to the Shared Service Provider
  • Under User Profiles and My Sites navigate to Personalization services permissions .
  • If the account doesn't already exist, add the account for which your sites App Domain is running under.
  • Grant that user Manage user profiles permission

    Another way is to use web services as described below.

    The idea is to use the UserProfileService.asmx. (http://localhost/_vti_bin/userprofileservice.asmx)

    For this as a first step you must create a web reference to this service and give a name to this reference.

    image

    In my case the property is a string. First instantiate your object:

    public static UPS.UserProfileService ups = new UPS.UserProfileService();



    The code is simple. Please remember that the user under the application pool is running must have the “Manage user profiles” permission.




    public static string GetTheCurrentUserPropertyFromWS()
    {
    String theproperty = String.Empty;
    ups.Url = GetServiceUrl();
    ups.Credentials = System.Net.CredentialCache.DefaultCredentials;

    SPSecurity.RunWithElevatedPrivileges(delegate()
    {
    try
    {
    string userName = System.Web.HttpContext.Current.User.Identity.Name;
    UPS.PropertyData propertyData = GetPropertyFromUser(userName);

    if (propertyData != null && propertyData.Values.Length != 0 && !string.IsNullOrEmpty(propertyData.Values[0].Value as string))
    {
    theproperty = (string)propertyData.Values[0].Value;
    }
    }
    catch (Exception ex)
    {
    throw new NullReferenceException(string.Format("Error: {0} ; User profile for user {1} not found."
    , ex.Message + ups.Url
    , System.Web.HttpContext.Current.User.Identity.Name));
    }
    });

    return theproperty;
    }

    private static bool SetPropertyForCurrentUserFromWS(string propertyAsString)
    {
    bool allOK = true;
    ups.Url = GetServiceUrl();
    ups.Credentials = System.Net.CredentialCache.DefaultCredentials;

    SPSecurity.RunWithElevatedPrivileges(delegate()
    {
    try
    {
    string userName = System.Web.HttpContext.Current.User.Identity.Name;

    UPS.PropertyData propertyData = GetPropertyFromUser(userName);
    if (propertyData != null)
    {
    UpdatePropertyToUser(userName, propertyAsString);
    }
    }
    catch (Exception ex)
    {
    allOK = false;
    throw new NullReferenceException(string.Format("User profile for user {0} not found."
    , System.Web.HttpContext.Current.User.Identity.Name + ups.Url));
    }
    });

    return allOK;
    }

    private static UPS.PropertyData GetPropertyFromUser(string userName)
    {
    UPS.PropertyData returnpropertyData = null;
    UPS.PropertyData[] userpropertydata = ups.GetUserProfileByName(userName);
    foreach (UPS.PropertyData pd in userpropertydata)
    {
    if (pd.Name == "MyPropertyName")
    {
    returnpropertyData = (UPS.PropertyData)pd;
    break;
    }
    }
    return returnpropertyData;
    }

    private static void UpdatePropertyToUser(string userName, string result)
    {
    // the property already exist - must only update
    UPS.PropertyData[] newdata = new UPS.PropertyData[1];
    newdata[0] = new UPS.PropertyData();
    newdata[0].Name = "MyPropertyName";
    newdata[0].Values = new UPS.ValueData[1];
    newdata[0].Values[0] = new UPS.ValueData();
    newdata[0].Values[0].Value = result;
    newdata[0].IsValueChanged = true;
    ups.ModifyUserPropertyByAccountName(userName, newdata);
    }

    private static string GetServiceUrl()
    {
    StringBuilder sb = new StringBuilder();

    string suffixUrl = "_vti_bin/userprofileservice.asmx";
    Uri theUri = new Uri(SPContext.Current.Site.Url);
    string prefixUrl = theUri.AbsoluteUri;
    if (theUri.AbsolutePath != @"/")
    {
    prefixUrl = prefixUrl.Replace(theUri.AbsolutePath, "");
    sb.Append(prefixUrl).Append(@"/").Append(suffixUrl);
    }
    else
    {
    sb.Append(prefixUrl).Append(suffixUrl);
    }

    return sb.ToString();
    }





    Now you can get or set values for user properties. Remember that the property must be already created. You cannot create one using a web service. 






  • Thursday, June 25, 2009

    Redirect to list item page knowing only item id, list id and web id

    Is never to late to learn something new and useful.

    My situation was that I had to open a list item page knowing only the ids of the item, the item container (list in my case) and web. Searching on the net I did not found what I needed. This was until a colleague told me about CopyUtil page from SharePoint and indeed I found a very interesting article from Jan Tielen.

    http://weblogs.asp.net/jan/archive/2008/02/26/copyutil-aspx-a-little-sharepoint-gem.aspx

    Enjoy !

    Wednesday, June 17, 2009

    Delete list items with Web Services and JQuery

    We can delete multiple list items calling UpdateListItems web method from Lists.asmx web service provided by SharePoint. This method can be called using JQuery ajax method.

    In my example I want to delete more items from a list knowing the items id’s. In user interface I have a collection of checkboxes with “id” attribute as id of the list item. Using a selector we obtain the collection of checkboxes, and build using the id attribute of checked one’s the batch that we will send to web method. “Tests” is the list name where I want to delete. For each item we have to add a Method with a distinct id.

    function DeleteSelected() {
    var chkcoll = $("myselector");
    var fields = "";
    chkcoll.each(function(i){
    if($(this).attr('checked') == true)
    {
    fields += "<Method ID=\""+$(this).attr('id')+"\" Cmd=\"Delete\"><Field Name=\"ID\">" + $(this).attr('id') + "</Field></Method>";
    }
    });

    var batch = "<Batch OnError=\"Continue\">" + fields + "</Batch>";
    var soapEnv = BuildSoapEnv(batch, 'Tests');
    theAjaxCallForUpdate(soapEnv, onSuccessDelete);
    }




    function BuildSoapEnv(batch, listname)
    {
    var soapEnv =
    "<?xml version=\"1.0\" encoding=\"utf-8\"?> \
    <soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \
    xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" \
    xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"> \
    <soap:Body> \
    <UpdateListItems xmlns=\"http://schemas.microsoft.com/sharepoint/soap/\"> \
    <listName>"
    + listname + "</listName> \
    <updates> \
    "
    + batch + "</updates> \
    </UpdateListItems> \
    </soap:Body> \
    </soap:Envelope>"
    ;
    return soapEnv;
    }



    To avoid the “The security validation” error thrown by the web service, the SOAPAction header must be provided using beforeSend option.



    The functionToCall is another function that you can call when the web method call is completed for some action that have to take place after. Also “success” option can be used and perform actions only if the calling of the web method was successfully.




    function theAjaxCallForUpdate(soapEnv, functionToCall)
    {
    $.ajax({
    url: textWebUrl + "/_vti_bin/Lists.asmx",
    beforeSend: function(xhr) {
    xhr.setRequestHeader("SOAPAction","http://schemas.microsoft.com/sharepoint/soap/UpdateListItems");},
    type: "POST",
    dataType: "xml",
    data: soapEnv,
    complete: functionToCall,
    contentType: "text/xml; charset=\"utf-8\""
    });
    }




    function onSuccessDelete()
    {
    // do something
    }



    The textWebUrl is a variable that contain the web url. We set this from our .cs code. The scripts are stored in a separate folder in _layouts. The entire Jquery script for calling the web service is in MyScript.js file. This file must be copied into _layouts/JQuery folder together with the last version of JQuery.




    StringBuilder sbJQ = new StringBuilder();
    sbJQ.Append("<script type=\"text/javascript\" src=\"_layouts/JQuery/jquery-1.3.2.min.js\"></script>");
    sbJQ.Append("<script type=\"text/javascript\">").Append("var textWebUrl='"+this.Web.Url+"';").Append("</script>");
    sbJQ.Append("<script type=\"text/javascript\" src=\"_layouts/JQuery/MyScript.js\" ></script>");

    if (!Page.ClientScript.IsClientScriptBlockRegistered("scriptkey"))
    {
    Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "scriptkey", sbJQ.ToString());
    }

    Wednesday, May 20, 2009

    Different behavior in New and Edit form for a custom field

    A request from a client was to create a custom field for a SharePoint list with different behavior in New form than in Edit. I was able to control this using JavaScript and the only thing left to do is to know in which form the field is rendered.

    For this I had to override the RenderFieldForInput method provided by BaseFieldControl class and check the SPControlMode.

    public class MyCustomField : BaseFieldControl 
    {
    ....

    protected override void RenderFieldForInput(HtmlTextWriter output)
    {
    string pathToScript = String.Empty;
    if (this.Field != null) pathToScript = (this.ControlMode == SPControlMode.Edit) ? "scriptforedit.js" : "scriptforother.js";
    output.Write("<script type=\"text/javascript\" src=\""+ pathToScript +"\" ></script>");
    base.RenderFieldForInput(output);
    }

    Tuesday, May 12, 2009

    Select parents in a TreeView using JQuery

    The requirement is to be able to select the parents of a node in a TreeView if a child is selected. Not only the first parent, all parents and to be able to unselect after if we want.

    If we look at generated source code we will notice that the parent node is in a div tag. Inside this div are the childrens inside a table/tr/td tags. So we must look for checkboxes inside a table data, table that is nested into a div. Is better to assign an attribute to the tree to be more easy to find. I added  the attribute “cipg” with the value “mpower”. The id of the tree is “theOne”.

    The code below solve this requirement. I’m sure this can be improved.

    <script type="text/javascript"> 

    function applyToParent(parent, treeId)
    {
    var actualId = treeId+'theOne';
    if (parent.attr('id')!= actualId)
    {
    var parentId = parent.attr('id').replace('Nodes', '');
    var theOne = $("div[cipg='mpower'] table tr td input[type='checkbox'][id^='"+parentId+"']");
    theOne.attr('checked', true);
    applyToParent(GetParent(theOne), treeId);
    }
    }

    function GetParent(theCurrent)
    {
    return theCurrent.parent().parent().parent().parent().parent();
    }

    function checkTheParent()
    {
    if($(this).attr('checked') == true)
    {
    var theParent = GetParent($(this));
    var currentId = $(this).attr('id');
    var treeId = currentId.substring(0,currentId.indexOf('theOne'));
    applyToParent(theParent, treeId);
    }
    }

    $(document).ready(function(){
    $("div[cipg='mpower'] table tr td input[type='checkbox']").bind('click', checkTheParent);
    });

    </script>

    Friday, May 8, 2009

    Display item type icon in SPGridView after search

    I had a request to display, after a search, an icon to make a visual distinction between types of every item matching the criteria. So after creating the query I had to render the SPGridView that I fill with the results.

    First was the problem of obtaining the link to the icon describing the type for each search result and after that the display.

    For the display part I add a field to grid using TemplateField:

    //add type image column for Type
    TemplateField typeCol = new TemplateField();
    typeCol.HeaderText = “Type”;
    typeCol.ItemTemplate = new ItemType("TheId", theList);
    grdMain.Columns.Add(typeCol);





    Here the grdMain is the SPGridView object and “TheId” is a managed property mapped to ows_ID crawled property. If you use CAML query you have to get the ID of the object into the TheId column.



    Now I had to create the class that helped me to display properly the icon. This class inherit ITemplate. In the “InstantiateIn” method we have to write the following:




    public void InstantiateIn(Control container)
    {
    Image img = new Image();
    img.DataBinding += new EventHandler(img_DataBinding);
    container.Controls.Add(img);
    }



    As parameters into the constructor of this class we receive the name of the displayed column and the list containing the searched object. Using this data we can get all needed information. Here is the code:




    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using Microsoft.SharePoint.WebControls;
    using Microsoft.SharePoint;

    namespace Com.CFG.WebParts.SearchResultsDocs
    {
    /// <summary>
    /// Implements a custom field which serves as a hyperlink
    /// </summary>
    class ItemType : ITemplate
    {
    private String _ColumnDisplay;
    private SPList _theList;

    #region ITemplate Members

    public void InstantiateIn(Control container)
    {
    Image img = new Image();
    img.DataBinding += new EventHandler(img_DataBinding);
    container.Controls.Add(img);
    }

    /// <summary>
    /// Initializes the hyperlink field
    /// </summary>
    /// <param name="ColumnDisplay">The name of the column which contains the value to be displayed</param>
    public ItemType(String ColumnDisplay, SPList theList)
    {
    _ColumnDisplay = ColumnDisplay;
    _theList = theList;
    }

    void img_DataBinding(object sender, EventArgs e)
    {
    Image img = (Image)sender;

    SPGridViewRow container = (SPGridViewRow)img.NamingContainer;

    //display name
    string itemId = (DataBinder.Eval(container.DataItem, _ColumnDisplay)).ToString();
    string imageurl = GetTypeIconLink(_theList, Int32.Parse(itemId));

    if (imageurl.Length > 0)
    {
    img.ImageUrl = "/_layouts/images/" + imageurl;
    img.Width = Unit.Pixel(16);
    img.Visible = true;
    }
    else
    {
    img.Visible = false;
    }
    }

    private string GetTypeIconLink(SPList faqList, int itemId)
    {
    string returnStr = String.Empty;

    SPFile afile = faqList.GetItemById(itemId).File;
    if (afile != null)
    {
    returnStr = afile.IconUrl;
    }
    return returnStr;
    }

    #endregion
    }
    }



    If we do not use this class and simply want to add this column to the SPGridView using this code:




    SPBoundField acol = new SPBoundField();
    acol.HeaderText = "Type";
    acol.DataField = "TheId";
    acol.Visible = true;
    grdMain.Columns.Add(acol);





    what we will see into the grid is something like



    <img src=”/_layouts/images/…” instead of the icon and this not what we want.



    As you noticed from the code, the way to get the path to icon is to use the IconUrl property of item attached file:




    private string GetTypeIconLink(SPList faqList, int itemId)
    {
    string returnStr = String.Empty;

    SPFile afile = faqList.GetItemById(itemId).File;
    if (afile != null)
    {
    returnStr = afile.IconUrl;
    }
    return returnStr;
    }



    Problem solved.