Showing posts with label SharePoint. Show all posts
Showing posts with label SharePoint. Show all posts

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);
    }

    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.

    Managed Property Creator tool

    In interface everybody knows to create Managed Properties used by MOSS search. Those can also be created programmatically very simple. This tool is created to be used by the admins on deployment time. Using this tool the admins save time and they are sure that all managed properties were created with no error.

    This tool use a configuration file describing the managed properties name, description mapped properties etc. This file must be updated before the tool is used. Here is an example of this file look like:

    <MappedProperty>
    <Name>TheId</Name>
    <Description>My first test</Description>
    <UseInScopes>yes</UseInScopes>
    <DataType>integer</DataType>
    <CrawledProperties>
    <CrawledProperty>
    <Name>ows_ID</Name>
    </CrawledProperty>
    </CrawledProperties>
    </MappedProperty>



    This will create a managed property called “TheId” that will be used in scope search and mapped to the “ows_ID” crawled property. Can be zero, one or more crawled property mapped depending on how much are specified inside the CrawledProperties tag.



    Do not forget that a property will be crawled only if contain any data.



    For using this tool you have to specify two parameters. First is “-c” or “-d” if you want to create or delete. Second is the name of the configuration file.



    Having the code bellow you only have to compile and use.




    using System;
    using System.Collections.Generic;
    using System.Text;

    using Microsoft.SharePoint;
    using Microsoft.Office.Server;
    using Microsoft.Office.Server.Search;
    using Microsoft.Office.Server.Search.Administration;
    using System.Xml;

    namespace ManagedPropertyCreator
    {
    class Program
    {
    private static ManagedDataType GetManagedDataType(string strDataType)
    {
    ManagedDataType returnValue = ManagedDataType.Text;
    switch (strDataType)
    {
    case ("binary"):
    returnValue = ManagedDataType.Binary;
    break;
    case ("datetime"):
    returnValue = ManagedDataType.DateTime;
    break;
    case ("decimal"):
    returnValue = ManagedDataType.Decimal;
    break;
    case ("integer"):
    returnValue = ManagedDataType.Integer;
    break;
    case ("yesno"):
    returnValue = ManagedDataType.YesNo;
    break;
    default:
    returnValue = ManagedDataType.Text;
    break;
    }
    return returnValue;
    }

    private static bool GetUsedInScopes(string isused)
    {
    if (isused.CompareTo("no") == 0) return false;
    else return true;
    }

    private static Category GetSharePointCategory(Schema sspSchema)
    {
    CategoryCollection categories = sspSchema.AllCategories;
    foreach (Category category in categories)
    {
    if (category.Name.Contains("SharePoint"))
    {
    return category;
    }
    }
    return null;
    }

    private static bool GetUsageMode(string usageMode)
    {
    if (usageMode == "-c") return true;
    else return false;
    }

    static void Main(string[] args)
    {
    try
    {
    if (args.Length != 2)
    {
    Usage();
    return;
    }

    bool isCreating = GetUsageMode(args[0]);

    XmlDocument config_file = new XmlDocument();
    config_file.Load(args[1]);
    Console.WriteLine("Parsing configuration file... ");

    // get the site url
    string strURL = config_file.SelectSingleNode("/root/SiteName").InnerXml.ToString().Trim();

    SearchContext context;
    using (SPSite site = new SPSite(strURL))
    {
    context = SearchContext.GetContext(site);
    if (context != null) Console.WriteLine(String.Format("Connected to {0}...", strURL));
    else
    {
    Console.WriteLine(String.Format("Error : Cannot connect to {0} !", strURL));
    return;
    }
    }
    Schema sspSchema = new Schema(context);
    ManagedPropertyCollection properties = sspSchema.AllManagedProperties;

    // get mapped properties nodes
    XmlNode nodeMappedProperties = config_file.SelectSingleNode("/root/MappedProperties");
    foreach (XmlNode mpNode in nodeMappedProperties.ChildNodes)
    {
    string mpName = mpNode.SelectSingleNode("Name").InnerXml.ToString().Trim();
    string mpDesc = mpNode.SelectSingleNode("Description").InnerXml.ToString().Trim();
    string mpUseInScopes = mpNode.SelectSingleNode("UseInScopes").InnerXml.ToString().Trim();
    string mpDataType = mpNode.SelectSingleNode("DataType").InnerXml.ToString().Trim();

    // here code for create Managed Property
    Console.WriteLine("");
    Console.Write(String.Format("{0} <{1}> metadata property", (isCreating)?"Create":"Delete", mpName));
    if (properties.Contains(mpName))
    {
    if (isCreating)
    {
    Console.WriteLine(String.Format("\nINFO: Managed Property with name <{0}> already exists. Will be skipped...", mpName));
    continue; // already exist - skip this one
    }
    }
    else
    {
    if (!isCreating)
    {
    Console.WriteLine(String.Format("\nINFO: Managed Property with name <{0}> do not exists. Will be skipped...", mpName));
    continue; // do not exist - skip this one
    }
    }

    if (isCreating)
    {
    ManagedProperty newMP = properties.Create(mpName, GetManagedDataType(mpDataType));
    newMP.Description = mpDesc;
    newMP.EnabledForScoping = GetUsedInScopes(mpUseInScopes);

    MappingCollection mappings = new MappingCollection();
    XmlNode mpCrawledProperties = mpNode.SelectSingleNode("CrawledProperties");
    // code for map with crawled properties
    foreach (XmlNode mpCrawledProperty in mpCrawledProperties.ChildNodes)
    {
    string nameCrawledProperty = mpCrawledProperty.SelectSingleNode("Name").InnerXml.ToString().Trim();

    foreach (CrawledProperty property in GetSharePointCategory(sspSchema).GetAllCrawledProperties())
    {
    if (property.Name.CompareTo(nameCrawledProperty) == 0)
    {
    mappings.Add(new Mapping(property.Propset, property.Name, property.VariantType, newMP.PID));
    }
    }
    }
    newMP.SetMappings(mappings);

    // do not forget to update the new created mapped property
    newMP.Update();
    }
    else
    {
    foreach (ManagedProperty oldMP in properties)
    {
    if (oldMP.Name == mpName)
    {
    if (oldMP.DeleteDisallowed)
    {
    Console.WriteLine("\n DeleteDisallowed enabled for " + mpName + ". Delete failed.");
    continue;
    }
    oldMP.DeleteAllMappings();
    oldMP.Delete();
    Console.Write("\n"+mpName + " deleted.");
    break;
    }
    }
    }
    Console.Write("...OK"); Console.WriteLine("");
    }
    }
    catch (Exception ex)
    {
    Console.WriteLine(ex.ToString());
    }
    finally
    {
    Console.WriteLine("\n All done...press any key");Console.ReadKey();
    }

    }

    static void Usage()
    {
    Console.WriteLine("Managed Property Creator");
    Console.WriteLine("Usage: ManagedPropertyCreator.exe <usage_mod> <conf_file>");
    Console.WriteLine("<usage_mod> - Specify if the tool is used to create or delete metadata properties. Acceptev values: -c and -d");
    Console.WriteLine("<conf_file> - The file that contain the configuration data.");
    }

    }
    }





    And yes, the configuration file:




    <?xml version="1.0" encoding="utf-8"?>
    <!-- SiteName is the name of the site using the SharedServiceProvider -->
    <!-- DataType can be : binary, datetime, decimal, integer, text, yesno -->
    <!-- UseInScopes can be : yes or no -->
    <root>
    <SiteName>http://CF316431:555/ssp/admin</SiteName>
    <MappedProperties>
    <MappedProperty>
    <Name>ManagedProperty1</Name>
    <Description>My first test</Description>
    <UseInScopes>yes</UseInScopes>
    <DataType>text</DataType>
    <CrawledProperties>
    <CrawledProperty>
    <Name>ows_FileLeafRef</Name>
    </CrawledProperty>
    <CrawledProperty>
    <Name>ows_LinkedDocumentField</Name>
    </CrawledProperty>
    </CrawledProperties>
    </MappedProperty>
    <MappedProperty>
    <Name>ManagedProperty2</Name>
    <Description>My first test 2</Description>
    <UseInScopes>yes</UseInScopes>
    <DataType>text</DataType>
    <CrawledProperties>
    <CrawledProperty>
    <Name>ows_FormulaField</Name>
    </CrawledProperty>
    </CrawledProperties>
    </MappedProperty>
    </MappedProperties>
    </root>



    For using you only have to type



    ManagedPropertyCreator.exe –c mpc_config.xml

    Modify the fields order into New and Edit form of a list

    If you have created a list and want for some reasons to change the order of the fields into the New or Edit form you can do this using the interface or at list creation time, using the schema.xml.

    Using the interface you have to go to “List Settings” -> “Columns” -> “Column ordering” if your list has not content type enabled or “Settings” -> “Content Types”-> click on used content types -> “Columns” -> “Column order” in the other case.

    If you want to do this using schema.xml you have to go in the <ContentTypes> tag and get the ID of the referenced content type. Using this ID go to the part that describe your contenttype and referenced fields. The fields order is decided by declaration order. For example if we have:

    <FieldRefs>
    <FieldRef ID="{BAA1289C-E3B8-11DD-95C8-C09456D89593}" Name="Field1" DisplayName="Field1" />
    <FieldRef ID="{E7CD97AA-E3B8-12DD-861B-5F9656D89593}" Name="Field2" DisplayName="Field2" />
    <FieldRef ID="{B2AB2191-2376-4B2C-938A-74AA4D14FF36}" Name="Field3" DisplayName="Field3" />
    </FieldRefs>



    Then the fields order will be Field1, Field2, Field3. Now change the declaration order and you will see that the fields order will be changed also.



    If we do not use content types and still want to customise the fields order seems that a default content type is used and the name for this is “Item”. Since this is a customisation I think you have to declare a new content type and do the steps from above.



    Another way can be to create your own customized page for New and Display.

    MOSS Search DateTime comparation

    Using the MOSS search one of the criteria was to match some items to a DateTime criteria. I have to specify that I was using FullTextSqlQuery and I had to build the query into the code because some other custom property ware added as criteria for search.

    Now back to DateTime, all seems to work for dates greater (>) and less (<). My problem was matching on equal criteria. According to MSDN we must not use equal criteria because the way the date is stored into database. They stored with hour, minute, seconds, milliseconds etc. They say that using “=” you will get unpredictable search results.

    But since the client want this and we have to make the client happy I had to find a solution to this. Only way to solve the problem was instead of writing

    TimeStart = '2009-04-23'

    to write

    ( TimeStart >= '2009-04-22' ) AND (TimeStart < '2009-04-23' )

    as matching criteria.

    It seems to work in my case.

    Monday, April 6, 2009

    AddFieldAsXml bug

    Yes, there is a bug on this function allowing that the field created using this function to have after creation as InternalName the DisplayName.

    This can cause problems when you ranslate your field name into other languages. In this case when you will try to access your field an error will occure.

    This bug can be fixed with a small workaround. For this, into our function that create the field we have to get the InternalName , create the field and after that replace the InternalName of the new created field with the one that we saved before. This solution can be used together with a solution presented into a previous post.

    private void SetTheProperGUID(SPList catList, SPField catField, SPList pageLibList) 
    {
    string fieldXml = catField.SchemaXml;
    string strInternalName = catField.InternalName;
    string strDisplayName = catField.Title;
    Regex regex = new Regex(@"List=\""(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}\""");
    if (regex.IsMatch(fieldXml))
    {
    fieldXml = regex.Replace(fieldXml, "
    List=\"" + catList.ID.ToString("B").ToLower() + "\"");
    }
    // a SharePoint bug force us to make a workaround
    // DisplayName must be the same as InternalName at creation
    string oldValue = "DisplayName=\"" + strDisplayName + "\"";
    string newValue = "DisplayName=\"" + strInternalName + "\"";
    fieldXml = fieldXml.Replace(oldValue, newValue);
    catField.Delete();
    pageLibList.Fields.AddFieldAsXml(fieldXml);
    // now the DisplayName must be set with the proper value
    SPField fixedField = pageLibList.Fields[strInternalName];
    fixedField.Title = strDisplayName;
    fixedField.Update();
    }

    Wednesday, December 31, 2008

    Lookup column - get data from parent list

    In my previous post I shared a solution to replace in the CAML schema of a lookup field the GUID of the list from which the field get his data.
    Now the situation is that we want to get data from the parent list. Of cource that we can use the previous way but for this case there is a more easy way.
    The only thing we have to do is to specify the value of the List attribute to Self.
    In this case we have a list with categories and each category can have subcategories from the same list.

     
    <Field        
    ID="{D2C297A5-1D6C-480d-9203-F54169A3FB18}"
    Name="Category"
    DisplayName="Category"
    StaticName="Category"
    SourceID="http://schemas.microsoft.com/sharepoint"
    Group="MyGroup"
    Type="Lookup"
    ShowField="Title"
    List="Self"
    />

     

    Lookup column - replace GUID of referenced list

    When we create a lookup field using CAML we need to specify the name or GUID of the list from which this field gets data. Is possible that this list to be created using a feature so we will not know his GUID.
     
    <Field        
    ID="{DFFDD227-CC2C-4a6f-930C-44BD65CDA80F}"
    Name="Location"
    DisplayName="Location"
    StaticName="Location"
    SourceID="http://schemas.microsoft.com/sharepoint"
    Group="MyGroup"
    Type="Lookup"
    ShowField="Title"
    List="Locations"
    />





    I try to specify the name of the list in field CAML definition but I do not know why after creation the field point to a strange object with a different GUID that I expected to.


    The solution I found is to have a feature who can modify this GUID after the required list is created. I created a function that get the SchemaXml of the field and in this xml replace the GUID of the list. The required list and the field are send as parameters. After we have the proper SchemaXml we delete the old field and create a new one using this new schema. In the feature activation we can call this function.


    Here is the code:


     



    private void SetTheProperGUID(SPList catList, SPField catField)    
    {
    string fieldXml = catField.SchemaXml;
    Regex regex = new Regex(@"List=\""(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}\""");
    if (regex.IsMatch(fieldXml))
    {
    fieldXml = regex.Replace(fieldXml, "
    List=\"" + catList.ID.ToString("B").ToLower() + "\"");
    }
    catField.Delete();
    catList.Fields.AddFieldAsXml(fieldXml);
    catList.Update();
    }

    Thursday, December 18, 2008

    Delete items from large Lists

    Situation:

    We have a large list (more than 100.000 items). The request is to delete all the items.

    Solution:

    We can try to iterate through all items and delete. Of course this approach will generate performance problems. The best solution I found is to use a batch:

    private void ClearList(SPList taskList)        
    {
    StringBuilder sbDelete = BuildBatchDeleteCommand(taskList);
    taskList.ParentWeb.ProcessBatchData(sbDelete.ToString());
    }

    /// <summary>
    /// Builds a batch string with a list of all the items that are to be deleted.
    /// </summary>
    /// <param name="spList"></param>
    /// <returns></returns>
    private static StringBuilder BuildBatchDeleteCommand(SPList spList)
    {
    StringBuilder sbDelete = new StringBuilder();
    sbDelete.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Batch>");
    string command = "<Method><SetList Scope=\"Request\">" + spList.ID +
    "</SetList><SetVar Name=\"ID\">{0}</SetVar><SetVar Name=\"Cmd\">Delete</SetVar></Method>";
    foreach (SPListItem item in spList.Items)
    {
    sbDelete.Append(string.Format(command, item.ID.ToString()));
    }
    sbDelete.Append("</Batch>");
    return sbDelete;
    }



     
    More details on how to use batch : http://msdn.microsoft.com/en-us/library/cc404818.aspx