Using HttpHandler to deal with cross-domain requests from AngularJS app for SharePoint on-premises

Scenario:

When you go to the newsfeed in your personal site (mysite newsfeed), you can write post and target it to different sites you are following or on your personal feed.

Recently, I had to built the same functionality for one of the projects. The ideas was to build the UI using SharePoint’s REST APIs and AngularJS. I am not using Office365 so i still have SharePoint on-prem.

Problem:

Its quite simple as you would expect to retrieve the list of sites a user is following. You simply call this REST endpoint “../_api/social.following/my/followed(types=4)” and you have the result, no brainer. But only when you dig into the results, you get to know that even though the list is correct but not every site returned has newsfeed enabled. As a result, you also get those sites that have no newsfeed and these are the sites you don’t want to display. So, how do you eliminate those?

The problem is that there is no information in the JSON response that REST endpoint returns. The only option you have is to actually check for the SiteFeed feature on that web. So you get the site, open that web and see if this feature in enabled. Now this is all fine when you are on server side but if you are implementing your functionality using client side, you will end up dealing with cross-domain issues and you will get exception as it is treated as a cross-domain request by the browser.

Now if you were dealing with Office365, this could have been resolved using Microsoft Azure’s ACS to authenticate your app with SharePoint and all would have worked just fine. But that’s not the case when you are building your client app using jQuery or Angular and using REST APIs in SharePoint on-prem.

Solution:

So, after struggling quite a bit to somehow deal with this, I ended up creating the HttpHandler in which I retrieved the list of sites the current user is following. Then I iterated through that collection to check for the Site Feed feature and return the collection of sites only with site feed feature enabled and continue to building my AngularJS app as I was before hitting this issue.

I am not putting up the entire code or even complete code for my app here. I am adding some snippets demonstrating the solution. Here is the markup in my view. Its simply a drop down that lists the sites I’m following just like the one you get in SharePoint by default.

<!-- Here is the markup in the view (html file) of my angularjs app. I am putting just the markup for rendering the dropdown with required results and eliminating rest of the code for demonstration purpose -->
<!-- here, vm.followedSites is the model that im setting in my controller and select element is bound to that model -->
 <div>
      <select ng-model="vm.selectedValue" ng-options="value.Name for value in vm.followedSites">
          <option value="">everyone</option>
      </select>
</div>

Here is my controller that calls the handler to retrieve that data and set the model that my view is bound to.

/* to simplify things, i am demonstrating the underlying method that is calling in the httphandler to get the desired result 
 this method is inside my controller 
 */
 
 function loadFollowedSites(currentUser) {
            //construct the URL for httphandler to get the sites with newsfeed activated
            var url = "/_layouts/15/myapp/Handlers/ClientHandler.ashx?operation=getFollowedSitesWithNewsFeed&username="+ currentUser;
            
            //make request to the url and return the result back
            return $http.get(url).then(function (response) {
                return response.data;
            });
        }

And finally, here is the code that retrieves the list of sites i’m following in my HttpHandler

/**
Here is the code snippet from my HttpHandler
 
**/  
  public void ProcessRequest(HttpContext context)
  {
      context.Response.ContentType = "application/json";
      context.Response.ContentEncoding = Encoding.UTF8;
      
      string operation = context.Request.QueryString["operation"];
 
      if (!String.IsNullOrEmpty(operation))
      {
        /* Code removed */ 
        if (operation == "getFollowedSitesWithNewsFeed")
        {
            string userName = (context.Request.QueryString["username"] != null) ? context.Request.QueryString["username"] : String.Empty;
            List<SPSocialActor> newsFeedFollowedSites = GetFollowedSitesWithNewsFeed(context, userName);
            context.Response.Write(JsonSerializer.Serialize(newsFeedFollowedSites));
        }
        
        /* code remoed */
      }
 
  }
  
  //Retrieves the list of all the sites that specific user is following that has SiteFeed web feature enabled
  private List<SPSocialActor> GetFollowedSitesWithNewsFeed(HttpContext cxt, string username)
  {
      List<SPSocialActor> followedSitesWithNewsFeed = new List<SPSocialActor>();            
      SPServiceContext context = SPServiceContext.GetContext(cxt);
      UserProfileManager profileManager = new UserProfileManager(context);
 
      if (profileManager.UserExists(username))
      {
          UserProfile userProfile = profileManager.GetUserProfile(username);
          SPSocialFollowingManager followingManager = new SPSocialFollowingManager(userProfile, context);
 
          SPSocialActor[] followedSites = followingManager.GetFollowed(SPSocialActorTypes.Sites);
 
          foreach (var actor in followedSites)
          {
              try
              {
                  using (SPSite site = new SPSite(actor.Uri.AbsoluteUri))
                  {
                      SPWeb rootWeb = site.RootWeb;
                      
                      //Check for SiteFeed feature (web)
                      Guid featureGuid = new Guid("15a572c6-e545-4d32-897a-bab6f5846e18");
                      if (rootWeb.Features[featureGuid] != null)
                      {
                          followedSitesWithNewsFeed.Add(actor);
                      }
                  }
              }
              catch 
              { 
                //Handle exception
              }
          }
      }
      else
      {
        //Log - user doesn't exist
      }            
      
      
      return followedSitesWithNewsFeed;
  }

Please let me know if there’s something I have missed or if there is any better way of doing this. I will really appreciate it.

Thanks Cheers

Adding local variables on a page as a user control

Quite often you need to use both server side and client side approach to build SharePoint solution as both offer different level of flexibility and functionality. One of the benefits of using mixed approach is that you can utilize the strength of both approaches and solve the problems quite quickly and efficiently.

In this post I’m going to present a part of the solution where we had to cache one control during one of the recent projects.

It is a very common practice that we write separate .js file for our client side scripting and than we can add these file either on a page or on a master page that we can use later on in our controls underneath. This works totally fine but the only thing we need to be careful about external files is that we need to wait for them to load before we can use anything inside these files. That is also fine but in some situations but there might be a little delay until file is loaded and that delay might be of quite a significance.

To overcome this issue, we (Me and Magnus Hansson, who is the mastermind of this solution) recently came up with an approach where we actually created a user control and in that user control, we added some script to set some local variables that we needed to use in on our page.

Here is the markup of the user control (ascx control):

Markup:


<script type="text/javascript">
var Local = window.Local || {};
   Local.Variables = function () {
     return {
        currentRootWebUrl : '<asp:Literal runat="server" id="litRootWebUrl" />',
        currentWebUrl : '<asp:Literal runat="server" id="litCurrentWebUrl" />',
        currentUser : '<asp:Literal runat="server" id="litCurrentUser" />',
        currentLCID : '<asp:Literal runat="server" id="litCurrentLCID" />'
       }
} ();
</script>

and in the CodeBehind(.cs) we set these asp controls embedded in script above:


protected override void CreateChildControls()
{
      //Get current user's Web object and Root Web Object
      SPWeb currentWeb = SPContext.Current.Web;
      SPWeb currentRootWeb = currentWeb.Site.RootWeb;

      //Get Locale and user's login name
      litCurrentLCID.Text = SPContext.Current.Web.CurrencyLocaleID.ToString();
      litCurrentUser.Text = SPContext.Current.Web.CurrentUser.LoginName;
      litCurrentWebUrl.Text = currentWeb.Url;
      litRootWebUrl.Text = currentRootWeb.Url;

      base.CreateChildControls();
}

So what we have achieved so far is basically set of variables that are set from code behind in control. When executed, the output would be a JavaScript object named “Local” that has a public property called “Variables” that contains some more properties.

Now in order to see this, we need to add this control either on application page or master page (or any where you find it appropriate in your case).

We added in on Master Page as we needed to used this on a control inside master page. In order to add it on a master page, you can directly Register your user control on master page and then added reference to the control in header section or even create a delegate control to attach it with master page through feature.

Once, control is added, navigate to your site and see the browser output. You should find this section rendered in the view source:


var Local = window.Local || {};

Local.Variables = function () {

return {
    currentRootWebUrl : 'http://demo.local.com',
    currentWebUrl : 'http://demo.local.com/site/hr',
    currentUser : 'domain\\user1',
    currentLCID : '1033'
  }

} ();

Now as soon as DOM is loaded, you have your local variables present that you can use right away in your control further down in the DOM (or controls etc) without waiting for any other JavaScript file to load like following:


    var currentRootWebUrl = Local.Variables.currentRootWebUrl;
    var currentWebUrl = Local.Variables.currentWebUrl;