Archive

Posts Tagged ‘Share Point’

SharePoint: Find Field Control

February 23rd, 2011 No comments

     In page code-behind I need to get a control related to some field. It’s considered the best practice is using of SPContext.Current.FormContext.FieldControlCollection. It’s an ArrayList that stores the field controls in the form. The code can look like the following:

public static FieldMetadata FindFieldControl(string fieldName)
{
    if (SPContext.Current.FormContext != null)
    {
        foreach (Control control in SPContext.Current.FormContext.FieldControlCollection)
        {
            FieldMetadata formField = control as FieldMetadata;
            if (formField != null && string.Compare(formField.FieldName, fieldName, true) == 0)
                return formField;
        }
    }
    return null;
}

     But the problem is that this collection is still empty or filled short on some early stages of page life cycle. In the same time the required field control can be already in the page tree of controls (Page.Controls).

     In my case to create a new list item we use a custom aspx-page. This aspx is very similar to the original SharePoint new-page for lists, the main difference is in the attribute Inherits in page declaration (<%@ Page … Inherits=”MyPageClass” … %>). Such approach allows us to do something in code-behind without redefinition of the standard html layout and, as the result, allows to have one page for many content types, because a set of field controls for every content type is still rendered by SharePoint infrastructure. But let’s get back on track.

     Let’s see the following example:

public class MyPageClass : WebPartPage
{
    protected override void OnInitComplete(EventArgs e)
    {
        FieldMetadata fldTmp = FindFieldControl("Model");
        // fldTmp = null, because the field control isn't created yet, SPContext.Current.FormContext.FieldControlCollection is empty


        base.OnInitComplete(e);


        fldTmp = FindFieldControl("Model");
        // fld = null, SPContext.Current.FormContext.FieldControlCollection isn't empty, but it doesn't contain the required field control,
        // in the same time the required field control is already in page tree of controls (Page.Controls)
    }


    protected override void OnLoad(EventArgs e)
    {
        FieldMetadata fldTmp = FindFieldControl("Model");
        // fld = null, SPContext.Current.FormContext.FieldControlCollection still doesn't contain the required field control,
        // but the required field control is presented in page tree of controls (Page.Controls)


        base.OnLoad(e);

        fldTmp = FindFieldControl("Model");
        // finally, fldTmp isn't null, SPContext.Current.FormContext.FieldControlCollection contains the required field control
    }
}

     Obviously, to get the field control as earlier as possible, we can use page tree of controls. It can be implemented in this way:

public static FieldMetadata FindFieldControlRecursive(Control root, string fieldName)
{
    FieldMetadata fieldMetadata = root as FieldMetadata;
    if (fieldMetadata != null && string.Compare(fieldMetadata.FieldName, fieldName, true) == 0)
        return fieldMetadata;

    foreach (Control c in root.Controls)
    {
        FieldMetadata t = FindFieldControlRecursive(c, fieldName);
        if (t != null)
            return t;
    }

    return null;
}

     The minus of this method is that it’s a recursive method and requires more time than if we use SPContext.Current.FormContext.FieldControlCollection. But we have to use FindFieldControlRecursive, if we need to get field controls at the early stage of page life cycle.

     The better way is to combine two these approaches into one method. The result looks like the following:

public static FieldMetadata FindFieldControl(Control root, string fieldName)
{
    if (SPContext.Current.FormContext != null)
    {
        foreach (Control control in SPContext.Current.FormContext.FieldControlCollection)
        {
            FieldMetadata formField = control as FieldMetadata;
            if (formField != null && string.Compare(formField.FieldName, fieldName, true) == 0)
                return formField;
        }
    }

    return FindFieldControlRecursive(root, fieldName);
}
public static FieldMetadata FindFieldControlRecursive(Control root, string fieldName)
{
    FieldMetadata fieldMetadata = root as FieldMetadata;
    if (fieldMetadata != null && string.Compare(fieldMetadata.FieldName, fieldName, true) == 0)
        return fieldMetadata;

    foreach (Control c in root.Controls)
    {
        FieldMetadata t = FindFieldControlRecursive(c, fieldName);
        if (t != null)
            return t;
    }

    return null;
}

     After this we will have

public class MyPageClass : WebPartPage
{
    protected override void OnInitComplete(EventArgs e)
    {
        FieldMetadata fldTmp = FindFieldControl(this, "Model");
        // fldTmp = null, because the field control isn't created yet


        base.OnInitComplete(e);


        fldTmp = FindFieldControl(this, "Model");
        // fldTmp isn't null, fldTmp was found in page tree of controls (Page.Controls)
    }


    protected override void OnLoad(EventArgs e)
    {
        FieldMetadata fldTmp = FindFieldControl(this, "Model");
        // fldTmp isn't null, fldTmp was found in page tree of controls (Page.Controls)


        base.OnLoad(e);

        fldTmp = FindFieldControl(this, "Model");
        // fldTmp isn't null, fldTmp was found in SPContext.Current.FormContext.FieldControlCollection
    }
}

     Don’t forget to use Static field name, do not use Display name. Thanks for attention!

Related posts:

SharePoint: Get SPControlMode, which is associated with the current request

December 30th, 2010 No comments

     As I said before, I have custom display/edit/new forms for every usable list. Some of them are derived from a page base class. In some cases, e.g. basing on characteristics of a list item we are working at, I have to limit access to the forms (display/edit/new) for some users. These additional restrictions is a sort of superstructure over the SharePoint groups and permissions. I try to check conditions and deny access to the page as soon as possible in bounds of the page lifecycle. I prefer doing it in the base class inside OnInit-method, so it allows to avoid such actions as creation of controls, redirections and so on, that usually happen inside and between OnInit and OnLoad, and that are already vain if you deny access to the page, of course. In other words, it looks like the following:

public class MyBaseWebPartPage : WebPartPage
{
    protected override void OnInit(EventArgs e)
    {
        if(!DoesUserHaveAccess())
            SPUtility.HandleAccessDenied(new Exception("User doesn't have access!"));
        base.OnInit(e);
    }
}

     Very often to analyze page accessibility I need to know what mode (display/edit/new) corresponds to the current page, i.e. I need to know exactly whether it’s a display-page or an edit-page or a page to create a new item. SPContext.Current contains property FormContext, which, in turn, contains property FormMode. FormMode is a value of SPControlMode enumeration and indicates what mode is active now on page.

public enum SPControlMode
{
    Invalid = 0,
    Display = 1,
    Edit = 2,
    New = 3,
}

     The only problem with FormMode is that, in the most cases, this property returns Invalid value inside OnInit-method. But, there is another way to find out the current mode of page. In schema.xml of any list there is a section <Forms>, which contains urls of aspx-pages that correspond to Display, Edit and New form modes.

<?xml version="1.0" encoding="utf-8"?>
<List Name="SomeList" Title="Some list" Description="" Direction="0" BaseType="0" Url="Lists/SomeList" Type="100" Id="48C2DB5D-C74A-4246-ACB5-1FF536D37C19" xmlns="http://schemas.microsoft.com/sharepoint/" VersioningEnabled="TRUE" DisableAttachments="TRUE">
  <MetaData>
    <Views>...</Views>
    <Fields>...</Fields>
    <ContentTypes>...</ContentTypes>

    <Forms>
      <Form Type="DisplayForm" Url="MyDispForm.aspx" WebPartZoneID="Main" />
      <Form Type="EditForm" Url="MyEditForm.aspx" WebPartZoneID="Main" />
      <Form Type="NewForm" Url="MyNewForm.aspx" WebPartZoneID="Main" />
    </Forms>
  </MetaData>
</List>

     These aspx-pages are used for each item of the list, but they can be overridden by urls defined for content types.

     If you have different content types, you, probably, have a section <FormUrls> inside content type definitions in schema.xml. As I said above, Urls defined in this section override the Urls defined in the section <Forms>, but only for this content type.

<?xml version="1.0" encoding="utf-8"?>
<List Name="SomeList" Title="Some list" Description="" Direction="0" BaseType="0" Url="Lists/SomeList" Type="100" Id="48C2DB5D-C74A-4246-ACB5-1FF536D37C19" xmlns="http://schemas.microsoft.com/sharepoint/" VersioningEnabled="TRUE" DisableAttachments="TRUE">
  <MetaData>
    <Views>...</Views>
    <Fields>...</Fields>
    <ContentTypes>
      <ContentType ID="0x0100204CA6A33177A5488DA2681769D46781" Name="SomeTypeOfItem" Group="List Content Types" Description="Create a new list item.">
        <FieldRefs>...</FieldRefs>
        <XmlDocuments>
          <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
            <FormUrls xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
              <Display>Lists/SomeList/MyView.aspx</Display>
              <Edit>Lists/SomeList/MyEdit.aspx</Edit>
              <New>Lists/SomeList/MyNew.aspx</New>
            </FormUrls>
          </XmlDocument>
        </XmlDocuments>
        <Folder>...</Folder>
      </ContentType>
    </ContentTypes>

    <Forms>...</Forms>
  </MetaData>
</List>

     So, by turns comparing urls, which is defined for a content type and/or a list, with url of the current Page.Request, we can definitely detect the current form mode of the page. The given approach can be implemented as the following method:

protected SPControlMode GetCurrentControlMode()
{
    // if current control mode is already accessible and valid, return it
    if (SPContext.Current.FormContext != null && SPContext.Current.FormContext.FormMode != SPControlMode.Invalid)
        return SPContext.Current.FormContext.FormMode;

    // compare url of the current request with url of every page defined in list and/or in content type

    // get current content type
    SPContentType spContentType = GetCurrentContentType();

    // store information about three possible form modes
    Dictionary<SPControlMode, KeyValuePair<string, PAGETYPE>> controlModeInfos = new Dictionary<SPControlMode, KeyValuePair<string, PAGETYPE>>(3);
    controlModeInfos[SPControlMode.Display] = new KeyValuePair<string, PAGETYPE>(spContentType != null ? spContentType.DisplayFormUrl : null, PAGETYPE.PAGE_DISPLAYFORM);
    controlModeInfos[SPControlMode.Edit]    = new KeyValuePair<string, PAGETYPE>(spContentType != null ? spContentType.EditFormUrl    : null, PAGETYPE.PAGE_EDITFORM);
    controlModeInfos[SPControlMode.New]     = new KeyValuePair<string, PAGETYPE>(spContentType != null ? spContentType.NewFormUrl     : null, PAGETYPE.PAGE_NEWFORM);

    // check each form mode whether it corresponds to the current request
    foreach (SPControlMode spControlMode in controlModeInfos.Keys)
    {
        bool res = false;

        // get a form url, that is defined in content type
        string cntTypeFormUrl = controlModeInfos[spControlMode].Key;
        // check whether the form url corresponds to the current request
        if (!string.IsNullOrEmpty(cntTypeFormUrl))
            res = IsCurrentPage(cntTypeFormUrl);
        if (!res)
        {
            // get a form url, that is defined in list
            SPForm spForm = SPContext.Current.List.Forms[controlModeInfos[spControlMode].Value];
            // check whether the form url corresponds to the current request
            if (spForm != null)
                res = IsCurrentPage(spForm.Url);
        }

        // return control mode if a coincidence is found
        if (res)
            return spControlMode;
    }

    return SPControlMode.Invalid;
}

     This method uses collection SPContext.Current.List.Forms to get access to urls defined in section <Forms> and properties SPContentType.DisplayFormUrl, SPContentType.EditFormUrl and SPContentType.NewFormUrl of the current content type to get access to urls defined in section <FormUrls>. The method GetCurrentContentType() works fine inside OnInit and was described in my previous post (SharePoint: Get content type, which is associated with the current request). The method IsCurrentPage is cited below:

protected bool IsCurrentPage(string pageName)
{
    if (!string.IsNullOrEmpty(pageName))
    {
        pageName = pageName.Trim('/');
        int urlDelimeterIndex = pageName.LastIndexOf('/');
        if (urlDelimeterIndex >= 0)
            pageName = pageName.Substring(urlDelimeterIndex + 1);
        return Page.Request.Url.AbsolutePath.EndsWith(pageName);
    }
    return false;
}
Related posts:

SharePoint: Get content type, which is associated with the current request

December 29th, 2010 No comments

     In my project I’ve created custom display/edit/new forms for every usable list. The easiest way to get a SPContentType-instance, which is connected with the current Page, is a property SPContext.Current.ListItem.ContentType. Sometimes, I have to get the current content type inside OnInit-method of page lifecycle. If we edit or display an existed list item (edit/display-forms), we can do it easily, because SPContext.Current.ListItem is already defined and accessible. But, if we create a new list item (new-form), we don’t have a valid SPContext.Current.ListItem inside OnInit, because none is created yet. The following table demonstrates when (starting from what method of page lifecycle) SPContext.Current.ListItem.ContentType is already accessible:

Form

Accessible starting from

Edit OnInit
Display OnInit
New OnLoad

     To get the current content type inside OnInit of New-form, at first we need to extract ContentTypeId from query-string of Page.Request, then having ContentTypeId we receive the instance of SPContentType. It should be noted that SPContext has an internal method GetContentTypeThroughQueryString, which provides SPCOntentType-instance based on id of content type in the query-string.

     Of course, we can invoke this internal method through .Net Reflexion, but I prefer rewriting such methods, when it’s possible. So, now I have my own GetContentTypeThroughQueryString method:

protected SPContentType GetContentTypeThroughQueryString()
{
    string cntTypeIdStr = HttpContext.Current.Request.QueryString["ContentTypeId"];
    if (!string.IsNullOrEmpty(cntTypeIdStr))
    {                
        try
        {
            SPContentType spContentType = null;
            if (cntTypeIdStr.StartsWith("0x"))
            {
                SPContentTypeId contentTypeId = new SPContentTypeId(cntTypeIdStr);
                spContentType = SPContext.Current.List.ContentTypes[contentTypeId];
                if (spContentType == null)
                    spContentType = SPContext.Current.List.ContentTypes[SPContext.Current.List.ContentTypes.BestMatch(contentTypeId)];
            }
            else
            {
                using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
                {
                    int num = int.Parse(cntTypeIdStr, writer.FormatProvider);
                    spContentType = SPContext.Current.List.ContentTypes[num];
                }
            }

            return spContentType;
        }
        catch{}
    }
    return null;
}

     And the result method, that returns the current content type associated with the current request, is

protected SPContentType GetCurrentContentType()
{
    if (SPContext.Current.ListItem != null && SPContext.Current.ListItem.ContentType != null)
        return SPContext.Current.ListItem.ContentType;

    // try to get content type through query string
    return GetContentTypeThroughQueryString();
}

     GetCurrentContentType can be invoked inside any method of page lifecycle, even inside OnInit.

Related posts:

SharePoint: Add ‘onchange’-attribute to DropDownChoiceField

December 2nd, 2010 No comments

To change the values of controls logically connected to DropDownChoiceField, I decided to add a client-side JavaScript handler that fires when selected value of DropDownChoiceField is changed. Unfortunately, DropDownChoiceField doesn’t have a habitual collection of attributes. But DropDownChoiceField is a template control and contains DropDownList inside, which allows to add attributes. We need to extract DropDownList from DropDownChoiceField and process it. I think the better way to do that is the Page_PreRender or similar methods, because all child controls of DropDownChoiceField are already created by this moment. According to Reflector, the id of DropDownList is “DropDownChoice”. To get control I use recursive FindControl function.

protected DropDownChoiceField choiceField;
//..............
protected void Page_PreRender(object sender, EventArgs e)
{
    DropDownList ddl = FindControlRecursive(choiceField, "DropDownChoice") as DropDownList;
    if (ddl != null)
        ddl.Attributes.Add("onchange", "javascript:alert('Hello')");
}
public static Control FindControlRecursive(Control root, string id)
{
    if (root.ID == id)
        return root;

    foreach (Control c in root.Controls)
    {
        Control t = FindControlRecursive(c, id);
        if (t != null)
            return t;
    }

    return null;
}
Related posts:
Categories: ASP.NET, Share Point Tags: ,