Second Life of a Hungarian SharePoint Geek

July 1, 2018

HttpRequest.Url contains always the full URL of the current request, doesn’t it?

Filed under: Bugs, SP 2013 — Tags: , — Peter Holpar @ 20:53

Recently we had to create a Wellcome menu extension in SharePoint to make a custom application page available from all of the web site context, like standard lists and pages as well as standard and custom application pages. This custom application page provides its own functionality (irrelevant to the problem described in the post), and after the user performed the task on the page, and submitted it via a button click, he should be returned to the original page, where he invoked the custom page from the menu. As the condition, if the menu item should or should not be displayed for a specific user, depends on some complex criteria (omitted from the code snippets in the post), we decided to implement the menu item on the server side by our custom MenuItemTemplate.

The CustomAction definition sets GroupId as PersonalActions and Location as Microsoft.SharePoint.StandardMenu to have the menu item in the Wellcome menu. The ControlAssembly and ControlClass attributes identify the class, in that we implemented the solution.

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  3.   <CustomAction
  4.       Id="YourCustomIdAction"
  5.       GroupId="PersonalActions"
  6.       Location="Microsoft.SharePoint.StandardMenu"
  7.       ControlAssembly="$SharePoint.Project.AssemblyFullName$"
  8.       ControlClass="YourNamespace.DisplayCustomMenuItem"
  9.     >
  10.   </CustomAction>
  11. </Elements>

In the CreateChildControls method of our class we create a new MenuItemTemplate instance, set its properties and finally add it to the Controls collection. See the ClientOnClickNavigateUrl property, that we set by invoking the static GetRedirectionUrl method of our helper class.

  1. public class DisplayCustomMenuItem : WebControl
  2. {
  3.     protected override void CreateChildControls()
  4.     {
  5.         MenuItemTemplate item = new MenuItemTemplate();
  6.         item.Description = "Description of your menu extension";
  7.         item.Sequence = 1000;
  8.         item.Text = "Do something";
  9.         item.ClientOnClickNavigateUrl = Helper.GetRedirectionUrl();
  10.  
  11.         Controls.Add(item);
  12.     }
  13. }

The original version of the static GetRedirectionUrl method was this one:

  1. public static string GetRedirectionUrl()
  2. {
  3.     SPContext ctx = SPContext.Current;
  4.     HttpContext context = HttpContext.Current;
  5.     string absUrl = context.Request.Url.AbsoluteUri;
  6.     string result = string.Format("{0}{1}?{2}={3}", ctx.Web.Url, Constants.RedirectionPagePath, QueryString.Source, HttpUtility.UrlEncode(absUrl));
  7.  
  8.     return result;
  9. }

Constants and QueryString are static classes. Constants.RedirectionPagePath stores the site relative path of our custom application page, like "/_layouts/15/OurCustomFolder/CustomApplicationPage.aspx", QueryString.Source is the name of the query string parameter (like ‘CustomSource’) variable we used to specify the URL of the page, where the user selected the menu item, and to which the application page should redirect after the user finished his task.

Our application page is inherited from the Microsoft.SharePoint.WebControls.LayoutsPageBase base class. The relevant part is the Click event handler of our button. We read the value of original URL from the query string parameter QueryString.Source, and redirect the response accordingly:

  1. protected void DoSomething_Click(object sender, EventArgs e)
  2. {
  3.     // do something here
  4.  
  5.     // redirect to the original page
  6.     var sourceUrl = this.Request.QueryString[QueryString.Source];
  7.     if (!string.IsNullOrEmpty(sourceUrl))
  8.     {
  9.         LoggingService.LogMessage("Redirecting request to page: '{0}'", sourceUrl);
  10.         Response.Redirect(sourceUrl);
  11.     }
  12. }

We activated the feature in a site collection like http://YourSharePoint/SomeSiteCollection. While testing the application, we found that the redirection worked as expected for all of the list views and standard (web part or wiki) pages, but malfunctioned for any application pages, either for standard or custom ones. For example, if the start page, where the user selected the custom menu item from the Welcome menu, was the Site Settings page of the root web site or any other sub web site of our site collection, having a URL like http://YourSharePoint/SomeSiteCollection/_layouts/15/settings.aspx, our custom application was displayed, but after clicking the button, the request was redirected instead of the original Site Setting page to the Site Setting page of the root site collection of the web application (that means, to URL http://YourSharePoint/_layouts/15/settings.aspx).

After debugging the solution in the context of various site collections, it turned out, that the value of the HttpContext.Current.Request.Url for application pages refers always to the page in the context of the root site collection of the web application (that means a URL beginning with http://YourSharePoint/_layouts/15). Of course, this result is not what you expect if your site is not the root one.

After even more debugging and browsing several properties of the current SPContext and HttpContext objects in run-time, I have finally found a value in the HttpContext.Items collection (the one with the key Microsoft.SharePoint.SPGlobal.GetVTIRequestUrl), that contained exactly the same URL value that corresponded the site specific URL of the application pages, the one we needed to achieve our original goal.

So I rewrote the GetRedirectionUrl method to check if the URL returned by the HttpContext.Current.Request.Url property matches to the URL of the current web site (as it is returned by SPContext.Current.Web.Url) and if the values differ, use the value stored in the entry in the HttpContext.Items collection having the Microsoft.SharePoint.SPGlobal.GetVTIRequestUrl key (as long as it is available). The code snippet below displays the updated version of the method, that finally performs as we expected:

  1. public static string GetRedirectionUrl()
  2. {
  3.     SPContext ctx = SPContext.Current;
  4.     HttpContext context = HttpContext.Current;
  5.     string absUrl = context.Request.Url.AbsoluteUri;
  6.     string webUrl = ctx.Web.Url;
  7.     string vtiRequestUrlKey = "Microsoft.SharePoint.SPGlobal.GetVTIRequestUrl";
  8.     Uri vtiRequestUrl = context.Items.Contains(vtiRequestUrlKey) ? context.Items[vtiRequestUrlKey] as Uri : null;
  9.     string requestUrl = ((absUrl.IndexOf(webUrl) == 0) || (vtiRequestUrl == null)) ? absUrl : vtiRequestUrl.AbsoluteUri;
  10.     string result = string.Format("{0}{1}?{2}={3}", webUrl, Constants.RedirectionPagePath, QueryString.Source, HttpUtility.UrlEncode(requestUrl));
  11.  
  12.     return result;
  13. }

Lesson learned: be aware that the HttpContext.Current.Request.Url may return the wrong value for application pages, and use the value of the entry with the Microsoft.SharePoint.SPGlobal.GetVTIRequestUrl key in the HttpContext.Items collection instead.

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: