Friday, March 27, 2015

Building a Flexible SMS Response System

Building SMS response system was my long pending dream! A few years back, I worked with SMS Service provider. Back then, the rates were high and the interfaces were not so easy.  But today, most SMS services offer cheap rates and very intuitive and easy to use, well documented APIs. Twillio is my choice for today. Let’s begin with a problem that’s being addressed here:

Problem:

My service needs to receive an SMS message from customer and process it and respond with an SMS message. The ask is very simple but if you can imagine, processing these SMS messages requires a flexible and configurable architecture, instead of a lots of if..else statements. I am not tying to do text analytics here, I expect my users to send specific keywords in SMS. For example, if they need help, they may send “help me”, “options”, or even “*”! If they need a list of nearby bars, we expect them to send “bars near me” or “nearby bars”….. you got the idea!

My Design Goals:

1. Ability to process incoming message and determine a response

2. The logic should be flexible and configurable. For instance, if I expect my users to send “help me” in order to receive a help message from us, it should be easy and configurable so that I can provided additional phrases that users can send without changing the code.

3. Break the problems into smaller chunks and make sure each piece of code has single responsibility

Possible Solutions

Given the problem and the design goals, I can think of two possible solutions.

Alternate Solution 1: Have separate classes created and each understands only a specific incoming message and processes it. Consider each class as a node which has ability to understand the request and provide response. If you like, consider it as an HTTP Module. Then build some sort of framework which passes the incoming message to each node in sequential fashion until it finds a node which can process the message.

Alternate Solution 2: Leverage a Service Bus architecture. A message can be put in a queue and then you can one subscriber which can process the message. And yes, the subscriber can pull the message from Queue and use the Alternate Solution 1 (above) to process the message. You may also have multiple subscribers and use the Topics mechanism of Azure Service Bus.

DISCLAIMER: If you are looking for a most reliable and scalable way to process the SMS messages, I recommend Alternate Solution 2 but for the low volume and fairly reliable solution, Solution 1 is not a bad choice either. While solution 1 is not the best, I’ll talk about that in this post, leaving solution 2 for another blog post. I also do not claim that these are the only two solutions, there could be many ways to solve such problems at hand. Sending and receiving SMS using Twilio is very simple. I’ll not talk about the mechanics of sending and receiving SMS messages because that depends from one provider to another.

So let’s take a look how does it (Alternate Solution 1) look conceptually:

image

And let’s zoon in a little bit into the SMS Processing Pipeline component:

image

The SMS Node processor in our case is a singleton class which initializes (and caches in its on state – it’s a Singleton, after all) the pipeline (series of processing nodes). It also has a method to receive a message and send response by routing the message through processing node.

A processing node implements a contract

   1:  interface ISmsProcessorNode
   2:      {
   3:          void ProcessMessage(SmsInfo message, SmsProcessingEventProperties eventProps);
   4:      }




The message represents everything that a typical incoming SMS message may include. For example, Twilio passes you following information along with some other information which we will not care:


   1:   public class SmsInfo
   2:      {
   3:          public string Message { get; set; }
   4:          public string ReceivedFrom { get; set; }
   5:          public string FromCity { get; set; }
   6:          public string FromCountry { get; set; }
   7:          public string FromState { get; set; }
   8:          public string FromZip { get; set; }
   9:      }




SmsProcessingEventProperties serves as a mechanism to receive configuration from the Node Processor and also as a mechanism to send response/status back to the node processor.


   1:  public enum SmsProcessingEventStatus
   2:      {
   3:          Processed = 0,
   4:          Skipped = 1, 
   5:          Warning =2,
   6:          Error = 3
   7:   
   8:      }
   9:      public class SmsProcessingEventProperties
  10:      {
  11:          public SmsProcessingEventStatus Status { get; set; }
  12:          public string ErrorMessage { get; set; }
  13:          public XElement Configuration { get; set; }
  14:          public string Response { get; set; }
  15:      }



Finally, here is the event processor looks like:


   1:   public class XmlNodePipelineProcessor
   2:      {
   3:          private static volatile XmlNodePipelineProcessor instance;
   4:          private static object syncRoot = new Object();
   5:   
   6:          List<SmsProcessingNode> _nodes = new List<SmsProcessingNode>();
   7:   
   8:          private XmlNodePipelineProcessor() { }
   9:   
  10:          public static XmlNodePipelineProcessor Instance
  11:          {
  12:              get
  13:              {
  14:                  
  15:                  if (instance == null)
  16:                  {
  17:                      lock (syncRoot)
  18:                      {
  19:                          var processorFile = HttpContext.Current.Server.MapPath("~/App_Data/SmsNodeProcessors.xml");
  20:                          if (instance == null)
  21:                          {
  22:                              instance = new XmlNodePipelineProcessor();
  23:                              instance.InitializeFromXml(processorFile);
  24:   
  25:                          }
  26:   
  27:                      }
  28:                  }
  29:   
  30:                  return instance;
  31:   
  32:              }
  33:                  
  34:              
  35:          }
  36:   
  37:          #region Public Members
  38:          public IEnumerable<SmsProcessingNode> GetProcessingNodes()
  39:          {
  40:              return _nodes;
  41:          }
  42:          public string GetResponseFor(SmsInfo incomingMessage)
  43:          {
  44:              string response = string.Empty;
  45:   
  46:              foreach (SmsProcessingNode node in _nodes)
  47:              {
  48:                  try
  49:                  {
  50:                      Assembly assembly = Assembly.Load(node.Assembly);
  51:                      Object classInstance = assembly.CreateInstance(node.ClassName);
  52:                      ISmsProcessorNode processingNode = classInstance as ISmsProcessorNode;
  53:                      if (processingNode == null)
  54:                      {
  55:                          Trace.WriteLine(string.Format("Assembly:{0}, Class{1} is not an implementation of {2}.  Node-{3} will be ignored.", assembly, node.ClassName, typeof(ISmsProcessorNode).FullName, node.Name));
  56:                          continue;
  57:                      }
  58:                      SmsProcessingEventProperties eventProps = new SmsProcessingEventProperties()
  59:                      {
  60:                          Configuration = node.Configuration,
  61:                          ErrorMessage = string.Empty,
  62:                          Status = SmsProcessingEventStatus.Skipped
  63:                      };
  64:                      processingNode.ProcessMessage(incomingMessage, eventProps);
  65:   
  66:                      // Check the modified eventProps and take actions
  67:                      if (eventProps.Status == SmsProcessingEventStatus.Skipped)
  68:                          continue;
  69:   
  70:                      if (eventProps.Status == SmsProcessingEventStatus.Error)
  71:                      {
  72:                          Trace.WriteLine(string.Format("Incoming Msg:{0}, Node:{1}, Error:{2}", incomingMessage.Message, node.Name, eventProps.ErrorMessage));
  73:                          continue;
  74:                      }
  75:                      if (eventProps.Status == SmsProcessingEventStatus.Processed && node.ContinueAfterProcessing)
  76:                      {
  77:                          continue;
  78:                      }
  79:                      if (eventProps.Status == SmsProcessingEventStatus.Processed && !string.IsNullOrEmpty(eventProps.Response))
  80:                      {
  81:                          return eventProps.Response;
  82:                      }
  83:                      
  84:                  }
  85:                  catch (Exception ex)
  86:                  {
  87:   
  88:                      Trace.WriteLine(string.Format( "Node:{0} failed to process message:'{1}', error message: {2}", node.Name, incomingMessage.Message, ex.Message));
  89:                  }
  90:                  
  91:              }
  92:   
  93:              return string.Format( "Sorry, we could not process your message: {0}", incomingMessage.Message);
  94:          }
  95:          #endregion
  96:   
  97:          private void InitializeFromXml(string fileName)
  98:          {
  99:   
 100:   
 101:              XmlDocument doc = new XmlDocument();
 102:              try
 103:              {
 104:                  doc.Load(fileName);
 105:              }
 106:              catch (XmlException ex)
 107:              {
 108:                  Trace.Write(ex);
 109:   
 110:              }
 111:   
 112:              if (doc.DocumentElement.LocalName != "NodeProcessors")
 113:              {
 114:                  Trace.WriteLine("File does not contain NodeProcessors");
 115:                  return;
 116:              }
 117:   
 118:   
 119:              XmlNodeReader nodeReader = new XmlNodeReader(doc.DocumentElement);
 120:              XDocument xDoc = XDocument.Load(nodeReader);
 121:   
 122:              Dictionary<string, string> assemblyRefs = new Dictionary<string, string>();
 123:              foreach (var item in xDoc.Descendants("Assembly"))
 124:              {
 125:                  //TODO: validate that item has Name and Value attributes
 126:                  assemblyRefs.Add(item.Attribute("Name").Value, item.Attribute("Value").Value);
 127:              }
 128:   
 129:              foreach (var item in xDoc.Descendants("Node"))
 130:              {
 131:   
 132:   
 133:                  if (!item.HasAttributes
 134:                      || item.Attribute("Name") == null
 135:                      || string.IsNullOrEmpty(item.Attribute("Name").Value)
 136:                      || item.Attribute("AssemblyRef") == null
 137:                      || string.IsNullOrEmpty(item.Attribute("AssemblyRef").Value)
 138:                      || !assemblyRefs.ContainsKey(item.Attribute("AssemblyRef").Value)
 139:                      || item.Attribute("Class") == null
 140:                      || string.IsNullOrEmpty(item.Attribute("Class").Value))
 141:                  {
 142:   
 143:   
 144:                      continue;
 145:                  }
 146:                  // All data seems to be okay, process it
 147:                  SmsProcessingNode newNode = new SmsProcessingNode()
 148:                  {
 149:                      Assembly = assemblyRefs[item.Attribute("AssemblyRef").Value.Trim()],
 150:                      ClassName = item.Attribute("Class").Value.Trim(),
 151:                      Name = item.Attribute("Name").Value,
 152:                      StopOnError = item.GetAttributeAsBool("StopOnError", false),
 153:                      ContinueAfterProcessing = item.GetAttributeAsBool("ContinueAfterProcessing", false),
 154:                      Configuration = item.Element("Configuration") 
 155:                  };
 156:   
 157:                  _nodes.Add(newNode);
 158:              }
 159:   
 160:   
 161:             
 162:          }
 163:      }




A node representation is here:


   1:  public class SmsProcessingNode
   2:      {
   3:          public string Name { get; set; }
   4:          public string Assembly { get; set; }
   5:          public string ClassName { get; set; }
   6:   
   7:          private bool _continueAfterProcessing = false;
   8:   
   9:          public bool ContinueAfterProcessing
  10:          {
  11:              get { return _continueAfterProcessing; }
  12:              set { _continueAfterProcessing = value; }
  13:          }
  14:   
  15:          private bool _stopOnError;
  16:   
  17:          public bool StopOnError
  18:          {
  19:              get { return _stopOnError; }
  20:              set { _stopOnError = value; }
  21:          }
  22:   
  23:          public XElement Configuration { get; set; }
  24:         
  25:      }



The XML based Node Configuration will look something like this:


<?xml version="1.0" encoding="utf-8" ?>
<NodeProcessors>
  <References>
    <Assemblies>
      <Assembly Name="PhoneService" Value="FULL NAME OF YOUR ASSEMBLY"></Assembly>
    </Assemblies>
  </References>
    <Nodes>
      <Node Name="PersistMessageProcessor" AssemblyRef="PhoneService" Class="FULL NAME OF CLASS TO SAVE MESSAGE IN DATABASE" ContinueAfterProcessing="true" StopOnError="false">
        <Configuration>
 
        </Configuration>
      </Node>
      <Node Name="HelpTextProcessor" AssemblyRef="PhoneService" Class="FULL NAME OF A CLASS TO SEND A RESPONSE WHEN  INCOMING MESSAGE IS TO SEEK HELP" ContinueAfterProcessing="false" StopOnError="false">
        <Configuration>
          <CatchPhrases>
            <Phrase>help</Phrase>
            <Phrase>help me</Phrase>
            <Phrase>help please</Phrase>
            <Phrase>what are my options?</Phrase>
            <Phrase>options</Phrase>
          </CatchPhrases>
          <Response>
            YOU NEED HELP! HERE ARE YOUR OPTIONS:...
          </Response>
        </Configuration>
      </Node>
      
    </Nodes>
  
</NodeProcessors>




As you can see, there are two examples that I have configured. The intention of the first node is to save the message in the database. The intention of 2nd node is to figure out if user is looking for help and if so, send a help response which is also configurable. Note that Configuration element in above XML has particular schema, the framework (node processor) will pass the entire configuration and it will be up to the node implementation to figure out how to use it.


So how does my HelpTextProcessor look like? well, i think that’s a good example which complete the loop in understanding how exactly it works. And here it is:


 public void ProcessMessage(SmsInfo message, Models.SmsProcessingEventProperties eventProps)
        {
            
            if (eventProps.Configuration == null)
            {
                eventProps.Status = Models.SmsProcessingEventStatus.Skipped;
            }
            try
            {
                foreach (var phrase in eventProps.Configuration.Descendants("Phrase"))
                {
                    if (phrase.Value.ToLower() == message.Message.ToLower().Trim())
                    {
                        eventProps.Status = Models.SmsProcessingEventStatus.Processed;
                        eventProps.Response = eventProps.Configuration.Descendants("Response").FirstOrDefault().Value.Trim();
                    }
                }
            }
            catch (Exception ex)
            {
                eventProps.Status = Models.SmsProcessingEventStatus.Error;
                eventProps.ErrorMessage = ex.Message;
                
            }
            

Thursday, March 26, 2015

SharePoint 2013: Quick way to open all messages in ULS for a Correlation ID

SharePoint often gives a correlation ID which can be looked into ULS Logs to troubleshoot issues. Until recently, I used to go to individual servers and fire up the ULSViewer, to see the logs. Sometime, I used to access logs from other servers and open a multiple instances of ULS viewer. It was not always convenient to open multiple log files.

Recently, I came across a new PowerShell Command to Merge the log based on criteria given. Criteria could be Diagnostic Area, Category, Start Time, End time etc. For the purpose of this post, I’d use the correlation id.

So there is a Merge-SPLogFile Cmdlet to merge all log files from all servers and put it into a single file which can be opened into the ULS viewer.

Well, that’s good but I took it a step further to open the ULSViewer.exe and open the log file directly, saving myself some clicks!

Here is the script look like. It needs path to ULSViewer and prompt you for Correlation ID when you run the script.

   1:  $ulsViewerPath = "c:\Tools\ulsviewer\ulsviewer.exe"  
   2:    
   3:  $correlationId = Read-Host -Prompt "Enter Correlation ID"  
   4:    
   5:    
   6:  $fileName = [String]::Format("C:\temp\{0}-{1}.log", [DateTime]::Now.ToString("yyyyMMddHHmmss"), $correlationId);  
   7:  Merge-SPLogFile -Correlation $correlationId  -Path $fileName  
   8:    
   9:    
  10:  [Diagnostics.Process]::Start($ulsViewerPath,$fileName)  



So when I run this script with a correlation that SharePoint just gave me in UI, It saves the log file in C:\temp directory and automatically opens the ULS viewer with relevant log entries as shown below:


SNAGHTML208ce7a


Hope that helps you save some time.


Wednesday, February 18, 2015

Better SharePoint Content Authoring:: Improving Navigation To Lists/Libraries

Once again, I’ll start with a Picture that represents the purpose of this Post. As you can imagine, I try to make it easy for my content authors to Navigate to different lists and libraries where the user is supposed to navigate to manage the content.

image

Sure you can ask the users to go to “View All Site Content” or access some of these links for “Site Settings” but you are able to save thousands of clicks over a period of time by making these links available in “Site Actions” menu. They don’t have to remember to go to “All Site Content” to manage lists and “Site Settings” to manage the term store or the navigation. That’s very inconvenient for my content authors.

Doing this is actual simple. All you need to do is:

1. Understand that the Site Actions can be extended programmatically and declaratively. I’ll talk about declarative extension in this post. You can get a fair idea about what you can do by observing how SharePoint is doing it. And those customizations can be found here

image

2. You can have your own customizations by putting your entries into Master Page Gallery –> Editing Menu –> CustomSiteAction.xml

For some of above customizations, the content of CustomSiteAction.xml in master page gallery would look like this:

<?xml version="1.0" encoding="utf-8" ?>
<Console>
  <references>
    <reference TagPrefix="cms"
      assembly="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
      namespace="Microsoft.SharePoint.Publishing.WebControls.EditingMenuActions" />
 
  </references>
  <structure>
 
   
 
    <ConsoleNode
       DisplayText="Ashish's Extended Menu"
       Description="Helps you navigate to different areas of the site."
       UseResourceFile="false"
       MenuGroupId="310"
       Sequence="211"
       ImageUrl="/_layouts/images/ActionsSettings.gif"
       UserRights="BrowseDirectories|ManagePermissions|ManageWeb|ManageSubwebs|AddAndCustomizePages|ApplyThemeAndBorder|ManageAlerts|ManageLists|ViewUsageData"
       RequiredRightsMode="Any"
       PermissionContext="CurrentSite"
       NavigateUrl="javascript:"
       UIVersion="4"
       ID="ASHISH_Manage">
      <ConsoleNode NavigateUrl="_layouts/AreaNavigationSettings.aspx"
           DisplayText="Quick Launch Links"
           Description="Manage Quick Launch Area Links"
           PermissionContext="CurrentSite"
           UseResourceFile="false"
           IsSiteRelative="true"
           ImageUrl="/_layouts/images/ActionsSettings.gif"
           ID="NavSettings"
           MenuGroupId="310"
           Sequence="101"
           UserRights="ManagePermissions|ManageWeb|ManageSubwebs|AddAndCustomizePages|ApplyThemeAndBorder|ManageAlerts|ManageLists|ViewUsageData"
           RequiredRightsMode="Any"/>
      <ConsoleNode NavigateUrl="PublishingImages/Forms/Thumbnails.aspx"
          DisplayText="Images"
          Description="Takes you to images library"
          PermissionContext="CurrentSite"
          UseResourceFile="false"
          IsSiteRelative="true"
          ImageUrl="/_layouts/images/ital.png"
          ID="BrowseImages"
          MenuGroupId="310"
          Sequence="102"
          UserRights="AddListItems|EditListItems"
          RequiredRightsMode="Any"/>
      <ConsoleNode NavigateUrl="Lists/faqs"
             DisplayText="Frequently Asked Querstions"
             Description="Manage FAQs displayed on the site."
             PermissionContext="CurrentSite"
             UseResourceFile="false"
             IsSiteRelative="true"
             ImageUrl="/_layouts/images/itgen.png"
             ID="BrowseFAQS"
             MenuGroupId="310"
             Sequence="103"
             UserRights="AddListItems|EditListItems"
             RequiredRightsMode="Any"/>
      <ConsoleNode NavigateUrl="Lists/Announcements"
         DisplayText="Announcements"
         Description="Manage Announcements Displayed on the site."
         PermissionContext="CurrentSite"
         UseResourceFile="false"
         IsSiteRelative="true"
         ImageUrl="/_layouts/images/announce.gif"
         ID="BrowseAnnouncements"
         MenuGroupId="310"
         Sequence="104"
         UserRights="AddListItems|EditListItems"
         RequiredRightsMode="Any"/>
      
      <ConsoleNode NavigateUrl="Pages/Forms/AllItems.aspx"
            DisplayText="Pages"
            Description="Takes you to pages library"
            PermissionContext="CurrentSite"
            UseResourceFile="false"
            IsSiteRelative="true"
            ImageUrl="/_layouts/images/itdl.png"
            ID="BrowsePages"
            MenuGroupId="310"
            Sequence="106"
            UserRights="AddListItems|EditListItems"
            RequiredRightsMode="Any"/>
 
 
    </ConsoleNode>
 
  </structure>
 
</Console>




Note that you can show/hide these links based on the permissions. I think this is a great way to make a difference in life of your SharePoint content authors.




Saturday, February 14, 2015

AngularJS: Building a Directive that Helps Choose a Date

This is my first AngularJS posts! I am so excited to get it started…

I just finished working on my third AngularJS project. I have used AngularJS in Provider Hosted, SharePoint Hosted and an IIS web application now. I must say that my AngularJS skills and code got better with every project. My last two projects included all best practices mentioned by John Papa: https://github.com/johnpapa/angularjs-styleguide

I wrote a whole bunch of directives and I will talk about them in future posts. I’ll start with very simple (and smart :)) directive. Basically, it helps my users to choose a date quickly. I used a date picker that comes with the UI bootstrap: http://angular-ui.github.io/bootstrap/ to get started and provided my custom directive on top of it which helps you select today’s with one click and move the date forward or backward by a configurable interval . See picture below:

SNAGHTML26a9d4b

You can use the directive like this:

<div st-date-provider st-config="{title:'Today', daysOffset:1}" st-model="vm.filterC.deliveryDate"></div>

The st-config represents a JavaScript object with following properties


















ParameterPurpose
titleRepresents the title that you want to display to represent “Today”
daysOffsetNumber of days you want to allow your users to move forward or backward.
minusTitleText you want to display for backward button. Optional.

For example, you can set daysOffset = 7 and set minusTitle to “- Week”
plusTitleText you want to display for forward button. Optional

Another way you can set it up is:

<div st-date-provider st-config="{title:'Today', daysOffset:7,  minusTitle:'Week', plusTitle:'Week'}" st-model="vm.filterC.deliveryDate"></div>



image


The dependencies for this directive is:


1. moment.js library for date manipulation


 


And finally the code for the Directive is:


app.controller('stDateProviderCtrl', ['$scope', 'common', function ($scope, common) {
       
$scope.provideDate = provideDate;
       
function provideDate(multiply) {
           
if (angular.isNumber($scope.stConfig.daysOffset)) {
               
if (!angular.isDate($scope.stModel)) {
                   
$scope.stModel = common.getToday();
                   
return;
               
}


               
$scope.stModel = moment($scope.stModel).add('days', $scope.stConfig.daysOffset * multiply).startOf('day').toDate();
           
}

       
}
   
}]);
   
app.directive('stDateProvider', ['config', function (config) {
       
var directive = {
           
restrict: 'EA',
           
controller: 'stDateProviderCtrl',
           
scope: {
               
stModel: "=",
               
stConfig: "="

           
},
           
template:   '<span>' + 
                            '<span class="pointer" ng-click="provideDate(-1)"><i class="fa fa-chevron-circle-left"></i></span>'
+
                            '<span class="pointer" ng-if="stConfig.minusTitle" ng-click="provideDate(-1)"> {{stConfig.minusTitle}}</span>'
+
                            '<span class="pointer" style="margin-left:10px;margin-right:10px" ng-click="provideDate(1)">{{stConfig.title}}</span>'
+
                            '<span class="pointer" ng-if="stConfig.plusTitle" ng-click="provideDate(1)">{{stConfig.plusTitle}} </span>'
+
                            '<span class="pointer" ng-click="provideDate(1)"><i class="fa fa-chevron-circle-right"></i></span>'
+
                        '</span>'
       
};
       
return directive;

   
}]);

 

Please leave your comments!


del.icio.us Tags: ,

Better SharePoint Content Authoring – Hide Sections that have no data

Presenting another trick for an improved SharePoint Content Authoring experience. As they say, picture says thousands words, I will present the problem and the solution in picture first:

Consider this page and the problem:

SNAGHTML16daf3

the solution:

SNAGHTML1a0e78

But what about this page in edit mode?

SNAGHTML223795

I know some of you may be thinking that this is so easy to do using JavaScript. Personally, I don’t like that approach because you will have to hide it based on element targeting and DOM manipulation. I think that’s not a robust solution because.. 1) it breaks when the element targeting fails (ID, or class name of DOM element changes 2) If you don’t implement it right, you will see “Description” first and then it  will magically disappear. That’s unacceptable for me.

Let’s talk about a better approach…

Well, my approach is it wrap the field heading (“Description” in this case)  into a Panel control which checks if the provided field has a value. If it has a value show the field heading panel, otherwise hide it.

It’s better because…..

1) It does not have two problems that I described above with JQuery approach

2) Unlike JavaScript approach, this solution is reusable. My favorite reason.

Here is my code for the panel look like:

public class FieldHeadingPanel : Panel, INamingContainer, IParserAccessor
   
{
       
private bool _shouldRender = false;
       
private bool _evaluated = false;

       
public string FieldGuid { get; set; }
       
public bool ShowInEditMode { get; set; }
       
protected override void AddParsedSubObject(object obj)
       
{
           
if (!_evaluated)
           
{
               
_evaluated = true;
               
this.ShouldRender();
           
}

           
if (_shouldRender)
           
{
               
base.AddParsedSubObject(obj);

           
}
       
}

       
private void ShouldRender()
       
{
           
_shouldRender = false;
           
try
           
{
               
if (!PublishingPage.IsPublishingPage(SPContext.Current.ListItem) || string.IsNullOrEmpty(FieldGuid))
               
{

                   
_shouldRender = false;
               
}
               
else
               
{
                   
// check if edit mode
                    if (SPContext.Current.FormContext.FormMode == Microsoft.SharePoint.WebControls.SPControlMode.Edit && ShowInEditMode)
                   
{
                       
_shouldRender = true;
                       
return;
                   
}
                   
SPField field = SPContext.Current.ListItem.Fields[new Guid(FieldGuid)];
                   
_shouldRender = !string.IsNullOrEmpty(field.GetFieldValueAsHtml(SPContext.Current.ListItem[new Guid(FieldGuid)]));


               
}
           
}
           
catch (Exception ex)
           
{
               
AppContext.FromSPContext().Logger.LogException(ex);
               
           
}
          
           

       
}
   
}

Basically, the panel will render content inside it only if the provided field has a value in it. I have tested it with Person, Text, Note and Summary Links field controls and it works. I’ll modify the code if it does not work for certain fields.

This class is part of my SharePoint Reusable Application framework which I use from one project to another.

You will put the following markup in your Page Layout.

   1:  <Framework:PageModePanel runat="server" ShowMode="Display" id="pnlDisplayOnly" >
   2:          <BEX:FieldHeadingPanel runat="server" FieldGuid="9da97a8a-1da5-4a77-98d3-4bc10456e700" ShowInEditMode="true">
   3:          <h2>
   4:              Description
   5:          </h2>
   6:     </Framework:FieldHeadingPanel>



Hope this helped you, feel free to leave comments!

Thursday, February 12, 2015

Better SharePoint Content Authoring – Page Creating Experience – Part 1

There is no doubt that Publishing is one of the greatest feature of SharePoint, but its unfortunate that authoring content (or should I say most) is not easy in SharePoint. Although Authors can be trained, I wish SharePoint provided a better authoring experience. I have worked in several publishing sites now and I try to implement several “authoring patterns” which makes the life of Authoring folks easier.

How about the most important thing for any Publishing Site – the experience of creating a page? Here is what I think about it!

SNAGHTMLbd811f 

So what’s wrong with this:

1. There is no way to choose what type of page you want to create.

2. There is no choice to choose layout of the page.

What that means is that, SharePoint will use the default content type and page layout to create the page. The author will then need to choose the right page layout from the ribbon. Seriously, I can understand the pain here, I wonder what was the SharePoint team thinking when making “New Page” functionality available from “Site Actions” menu. Sure, there are different ways to create a page which allows you to choose the page layout but then, why two different experiences?

Anyways, I like the Authors to create the new page from Site Actions Menu, so I generally prefer to replace that page with my own page. Essentially, I allow the users to choose the content type and optionally a page layout. The end result looks like this:

SNAGHTML10292b3

I will blog about the implementation of this pattern in Part 2 of this series. If you are curious now, it’s a two step process: 1) Create a custom Application Page with code-behind to create the page and redirect to edit page 2) Replace the “New Page” link in the “Site Actions” menu.

Friday, January 23, 2015

Single Page Apps in SharePoint – Lessons Learnt…

 

Recently I developed a single page SharePoint Hosted App with Angular JS and SharePoint Client Side Object Model. In this post, I am sharing the lessons learnt and some design suggestions to those who are interested in taking this route.

Feel free to leave your comments, if you have any questions about these ideas.

Get rid of the complex Url…

First, an Angular SPA’s routing mechanism requires the views to be loaded with {Your Main Page}#{view name} syntax. But when SharePoint launches the App, it provides several parameters which are required to call back to SharePoint services. Honestly, I hate those parameters so first time my app is launched, I store those into cookies and redirect to my SPA home page without those parameters. This makes the App URLs much more SPA friendly. It’s hard to put the code here because there are many moving pieces, but if you are interested in this approach, I recommend following course by Andrew:

Building SharePoint Apps as Single Page Apps with AngularJS

In addition to what Andrew’s approach, I also provided an deep linking ability in my app so that I can include links to item pages in emails being sent out from the App.

Store Configuration in Hidden List…

I prefer to store some absolutely critical but configurable items in a hidden configuration list which the support people (including me in some cases) can modify to slightly tweak the application behavior. Example of such configuration is given below:

SNAGHTML5a6c971

As you can see, I store the configuration about my Managed Metadata Controls, shared mail box, subject and body of my mailto links, whether or not the names are lync enabled and so on… I wrote the app to take these values instead of hard-coding them into the app. After all, I feel bad if the business has to come to me for every little change that they want to make so I always try to find a balance between amount of time it takes to make it configurable vs. the value it provides.

Store Data outside app-web (personal choice)…

You can store the data inside the app-web or outside of the app web, for instance, into the Host Web. I know this is a big topic and there are lot of conflicting opinions. Honestly, it drives me crazy that the data inside the app web is not searchable, that it gets deleted every time you re-install the app and that you lose rich functionality like Alerts. I don’t know about you, but these limitations are not acceptable to me. Why should I lose my data every time I redeploy the app into my development site? Am I supposed to enter the test data again (of course, you can write some script to insert the test data)?

Since I stored the data outside the app, I included links to this lists from my an administration page in my App (see below). Navigating through different things is not easy in SharePoint, I have heard a lot of complaints about it, so providing a one click access to all important things goes a long way, my users loved it.

SNAGHTML5b644b2

The question is how do these lists get created and configured? A truly independent app can provide a configuration page from which you can trigger JavaScript (REST/JSOM) scripts and have these lists created/configured. If you want to be more sophisticated, you can check for a flag on host web (Hint: Web Property) indicating a missing configuration and  then complete run the configuration script and turn on the flag so that it does not run again.

Considering tight deadlines though, I used custom PowerShell Scripts which uses a CSOM object model to create this lists and libraries. You may wonder why CSOM object model? It’s because I wanted it run them without accessing the servers’ powershell console, imagine O365!

Get hold of People Picker and Managed Metadata Controls…

There is a shortage of SPA friendly People Picker and the Managed Metadata pickers. I chose to get Javascript based implementation of these controls from /OfficeDev/Pnp (link below)

https://github.com/OfficeDev/PnP/tree/master/Components

And then, I wrote Angular JS Directives on top of these controls. If you want to know more about this, leave a comment, I can share some code pieces.

Centralize SharePoint Data Access…

While this is a no-brainer, I still see developers writing random JavaScript on scattered across the pages. Make a point to centralize all data access in logically separated repositories. Angular provides a great way to build Services, make a use of them and put your logic in repository services created in Angular.

Cache.. Cache.. Cache..

The data that is not frequently updated should be cached. I liked Angular Local Storage module (link below) which allowed me to store things like quick links, contacts, currently logged in user’s info and many other things into a local session store. It also allows you to use both HTML5 local storage and traditional cookie storage, use them as per your need.

https://github.com/grevory/angular-local-storage

This greatly improved the responsiveness of my pages. I also put refresh icon where such data was displayed on screen which allowed users to refresh the data from server on-demand.

Allow Business to change Email Templates

Emails are so common into a custom SharePoint Apps and yet, many developers hard code the email templates into the code. I believe email templates should be allowed to be changed by power users (let your power users feel powerful), so I built custom admin pages which allows my users to change the body of my email templates. On the email template edit screens, I provided them a list of special placeholders (example $$CURRENT_USER_ID$$, $$ITEM_TITLE$$, etc) which my application replaces with real values before sending out emails. That way, they had complete control over the subject and body of emails, the well-documented placeholders allowed them to make those template truly dynamic and useful.