Contents tagged with Controls

  • The SPDataSource Control

    Introduction

    SPDataSource, together with XsltListViewWebPart, DataFormWebPart and ContentByQueryWebPart, is one of the most powerful tools that SharePoint provides. Because it can be used in different ways, it can also get complicated. In this post I am going to talk about is different modes and what can they give us. Fist, you need to know that the mode comes from the DataSourceMode property and can be one of:

    Let’s see them one by one.

    Webs

    Webs mode returns subsites of the current site or the root site. You need to supply a select parameter to SPDataSource named WebID, which can take one of the following values:

    • RootWeb: returns subsites of the root site;
    • SubWeb: returns subsites of the current site;
    • <ID>: returns subsites of a given site, indicated by its ID GUID.

    You can also pass a WebURL parameter, taking the relative URL of a site, or {sitecollectionroot} for the root site of the current site collection.

    SPDataSource will return one row for each site found, and for each site, it will return attributes that correspond to the properties of SPWeb, with the __sp prefix. They are:

    __spAlerts
    __spAllowAnonymousAccess
    __spAllowGlobalCatalog
    __spAllowP2P
    __spAllProperties
    __spAllUsers
    __spAlternateCssUrl
    __spAlternateHeader
    __spAnonymousPermMask64
    __spAnonymousState
    __spAudit
    __spAuthenticationMode
    __spAuthor
    __spAvailableContentTypes
    __spAvailableFields
    __spConfiguration
    __spContentTypes
    __spContext
    __spCreated
    __spCurrencyLocaleID
    __spCurrentChangeToken
    __spCurrentUser
    __spCustomizationMode
    __spCustomJSUrl
    __spDataRetrievalServicesSettings
    __spDefaultUrl
    __spDescription
    __spDocTemplates
    __spEffectiveBasePermissions
    __spEffectivePresenceEnabled
    __spEventHandlersEnabled
    __spEventReceivers
    __spExecuteUrl
    __spExists
    __spExternalSecurityProviderSetting
    __spFeatures
    __spFields
    __spFieldTypeDefinitionCollection
    __spFiles
    __spFirstUniqueAncestor
    __spFirstUniqueAncestorWeb
    __spFirstUniqueRoleDefinitionWeb
    __spFolders
    __spGroups
    __spHasExternalSecurityProvider
    __spHasUniquePerm
    __spHasUniqueRoleAssignments
    __spHasUniqueRoleDefinitions
    __spID
    __spIgnoreCheckOutLock
    __spIncludeSupportingFolders
    __spIsADAccountCreationMode
    __spIsADEmailEnabled
    __spIsParsingWebPartPage
    __spIsRootWeb
    __spLanguage
    __spLastItemModifiedDate
    __spLists
    __spListTemplates
    __spLocale
    __spMasterUrl
    __spCustomMasterUrl
    __spMeetingCount
    __spMeetingInformation
    __spModules
    __spName
    __spParentWeb
    __spParentWebId
    __spPermissions
    __spPersonalizationMode
    __spPortalMember
    __spPortalName
    __spPortalSubscriptionUrl
    __spPortalUrl
    __spPresenceEnabled
    __spProperties
    __spRecycleBin
    __spRegionalSettings
    __spReusableAcl
    __spRoleAssignments
    __spRoleDefinitions
    __spRoles
    __spRootFolder
    __spSafeControls
    __spServerNow
    __spServerRelativeUrl
    __spSite
    __spSiteGroups
    __spSiteUserInfoList
    __spSiteUsers
    __spSyndicationEnabled
    __spTheme
    __spThemeCssUrl
    __spTitle
    __spTypeCache
    __spUrl
    __spUserInfoListId
    __spUserIsSiteAdmin
    __spUserIsWebAdmin
    __spUsers
    __spViewMode
    __spViewStyles
    __spWebs
    __spWebTemplate
    __spWebTemplateId

    ListOfLists

    ListOfLists returns lists of a given site. It expects a WebID (same values as per the Webs mode) or a WebURL (relative address of a subsite) parameter, if none is supplied, it defaults to the current site. This mode allows restricting the lists to return by applying a CAML query to the SelectCommand property. For example, the following query filters lists of template 101 = Document Library in any of the subsites:

       1: <Webs Scope='Recursive'></Webs> <Lists ServerTemplate='101'></Lists>

    SPDataSource returns the following fields, which are equivalent to the properties with the same name in the SPList class, minus the __sp prefix:

    __spAlertTemplate
    __spAllowContentTypes
    __spAllowDeletion
    __spAllowMultiResponses
    __spAnonymousPermMask
    __spAnonymousPermMask64
    __spAudit
    __spAuthor
    __spBaseTemplate
    __spBaseType
    __spCanReceiveEmail
    __spContentTypes
    __spContentTypesEnabled
    __spCreated
    __spCurrentChangeToken
    __spDefaultView
    __spDefaultViewUrl
    __spDescription
    __spDirection
    __spDraftVersionVisibility
    __spEffectiveBasePermissions
    __spEmailAlias
    __spEnableAssignToEmail
    __spEnableAttachments
    __spEnableMinorVersions
    __spEnableModeration
    __spEnableSyndication
    __spEnableVersioning
    __spEventReceivers
    __spEventSinkAssembly
    __spEventSinkClass
    __spEventSinkData
    __spExcludeFromTemplate
    __spFields
    __spFirstUniqueAncestor
    __spFlags
    __spForceCheckout
    __spForms
    __spHasExternalEmailHandler
    __spHasUniqueRoleAssignments
    __spHidden
    __spID
    __spImageUrl
    __spInternalName
    __spItemCount
    __spItems
    __spLastItemDeletedDate
    __spLastItemModifiedDate
    __spLists
    __spMultipleDataList
    __spOnQuickLaunch
    __spOrdered
    __spParentWeb
    __spParentWebUrl
    __spPermissions
    __spProperties
    __spPropertiesXml
    __spReadSecurity
    __spReusableAcl
    __spRoleAssignments
    __spRootFolder
    __spRootFolderUrl
    __spSchemaXml
    __spSendToLocationName
    __spSendToLocationUrl
    __spShowUser
    __spTitle
    __spVersion
    __spViews
    __spWorkflowAssociations
    __spWriteSecurity

    CrossList

    CrossList performs a query in a number of lists, in pretty much the same way as SPSiteDataQuery does, returning items from these lists. It needs a CAML query in SelectCommand, one that you would pass to SPQuery, and you should specify there the fields to return and an optional query, like the following, which returns all items created by the current user:

       1: <Query><Where><Eq><FieldRef Name='Author'/><Value Type='Number'><UserID/></Value></Eq></Where></Query>

    Regardless of those, it will always return the following fields:

    ListId
    WebId
    ID

    List

    List returns items from a single list. It expects a WebID or WebURL parameter, defaulting to the current site if none is present, plus ListName, which is, of course, mandatory, and optionally RootFolder or FolderID (for a starting folder name, from SPFolder.Name, like Path/SubPath, or id, from SPFolder.UniqueId). Do set the UseInternalName and IncludeHidden property to true in SPDataSource. There’s another property that affects the results of List, which is Scope. It is similar to SPQuery. It’s possible values are:

    • Default: shows files and subfolders in the starting folder;
    • Recursive: shows all files in all folders;
    • RecursiveAll: shows all files and all folders;
    • FilesOnly: shows all files in the starting folder.

    The fields that are returned depends on the actual list, but the following should be always present:

    ContentTypeId
    Title
    _ModerationComments
    File_x0020_Type
    HTML_x0020_File_x0020_Type.File_x0020_Type.mapall
    HTML_x0020_File_x0020_Type.File_x0020_Type.mapcon
    HTML_x0020_File_x0020_Type.File_x0020_Type.mapico
    WF4InstanceId
    ID
    ContentType
    Modified
    Created
    Author
    Author.id
    Author.title
    Author.span
    Author.email
    Author.sip
    Author.jobTitle
    Author.department
    Author.picture
    Editor
    Editor.id
    Editor.title
    Editor.span
    Editor.email
    Editor.sip
    Editor.jobTitle
    Editor.department
    Editor.picture
    _HasCopyDestinations
    _HasCopyDestinations.value
    _CopySource
    owshiddenversion
    owshiddenversion.
    WorkflowVersion
    WorkflowVersion.
    _UIVersion
    _UIVersion.
    _UIVersionString
    Attachments
    _ModerationStatus
    _ModerationStatus.
    LinkTitleNoMenu
    LinkTitle
    LinkTitle2
    SelectTitle
    InstanceID
    InstanceID.
    Order
    Order.
    GUID
    WorkflowInstanceID
    FileRef
    FileRef.urlencode
    FileRef.urlencodeasurl
    FileDirRef
    Last_x0020_Modified
    Created_x0020_Date
    Created_x0020_Date.ifnew
    FSObjType
    SortBehavior
    PermMask
    FileLeafRef
    FileLeafRef.Name
    FileLeafRef.Suffix
    UniqueId
    SyncClientId
    ProgId
    ScopeId
    HTML_x0020_File_x0020_Type
    _EditMenuTableStart
    _EditMenuTableStart2
    _EditMenuTableEnd
    LinkFilenameNoMenu
    LinkFilename
    LinkFilename2
    DocIcon
    ServerUrl
    EncodedAbsUrl
    BaseName
    MetaInfo
    MetaInfo.
    _Level
    _Level.
    _IsCurrentVersion
    _IsCurrentVersion.value
    ItemChildCount
    FolderChildCount
    AppAuthor
    AppEditor

    ListItem

    Finally, ListItem returns fields from a single list item. You need to feed SPDataSource parameters WebID/WebURL, ListName and ListItemID/ListItemGUID, where ListItemGUID corresponds to the UniqueId property of SPListItem and ListItemID to ID. The fields that are returned depend on the list and content type, of course, but the following are always returned, in the case of a list:

    ID
    ContentType
    Created
    Modified
    Modified_x0020_By
    Created_x0020_By

    Or of a document library:
    SelectFilename
    FileLeafRef
    _dlc_DocId
    _dlc_DocIdUrl
    _dlc_DocIdPersistId

    Parameters

    Parameters are supplied in the SelectParameters collection and normally are one or more of:

    In SelectCommand, you can use these parameters, by referring their Name inside {}, for example:

       1: <View><ViewFields><RowLimit>{Count}</RowLimit></ViewFields></View>

    Accessing Result

    You can access the results in one of several ways:

    • Bind the SPDataSource to a data web part that works with XSLT, such as DataFormWebPart;
    • Use a grid control, such as SPGridView, to display fields, but you have to add columns yourself; one disadvantage is that you cannot see the contents of complex properties, such as, for example, __spParentWeb (the ParentWeb property of SPWeb);
    • Use instead a data-bound control, such as Repeater, where you can access public properties of returned fields, using Eval, for example, <%# Eval("__spParentWeb.Url") %>.

    Conclusion

    Together with DataFormWebPart, SPDataSource is a precious tool, which offers great flexibility without going into code. Learn to use it and it will become a great friend!

    Read more...

  • Speech Recognition in ASP.NET

    Speech synthesis and recognition were both introduced in .NET 3.0. They both live in System.Speech.dll. In the past, I already talked about speech synthesis in the context of ASP.NET Web Form applications, this time, I’m going to talk about speech recognition.

    .NET has in fact two APIs for that:

    I am going to demonstrate a technique that makes use of HTML5 features, namely, Data URIs and the getUserMedia API and also ASP.NET Client Callbacks, which, if you have been following my blog, should know that I am a big fan of.

    First, because we have two APIs that we can use, let’s start by creating an abstract base provider class:

       1: public abstract class SpeechRecognitionProvider : IDisposable
       2: {
       3:     protected SpeechRecognitionProvider(SpeechRecognition recognition)
       4:     {
       5:         this.Recognition = recognition;
       6:     }
       7:  
       8:     ~SpeechRecognitionProvider()
       9:     {
      10:         this.Dispose(false);
      11:     }
      12:  
      13:     public abstract SpeechRecognitionResult RecognizeFromWav(String filename);
      14:  
      15:     protected SpeechRecognition Recognition
      16:     {
      17:         get;
      18:         private set;
      19:     }
      20:  
      21:     protected virtual void Dispose(Boolean disposing)
      22:     {
      23:     }
      24:  
      25:     void IDisposable.Dispose()
      26:     {
      27:         GC.SuppressFinalize(this);
      28:         this.Dispose(true);
      29:     }
      30: }

    It basically features one method, RecognizeFromWav, which takes a physical path and returns a SpeechRecognitionResult (code coming next). For completeness, it also implements the Dispose Pattern, because some provider may require it.

    In a moment we will be creating implementations for the built-in .NET provider as well as Microsoft Speech Platform.

    The SpeechRecognition property refers to our Web Forms control, inheriting from HtmlGenericControl, which is the one that knows how to instantiate one provider or the other:

       1: [ConstructorNeedsTag(false)]
       2: public class SpeechRecognition : HtmlGenericControl, ICallbackEventHandler
       3: {
       4:     public SpeechRecognition() : base("span")
       5:     {
       6:         this.OnClientSpeechRecognized = String.Empty;
       7:         this.Mode = SpeechRecognitionMode.Desktop;
       8:         this.Culture = String.Empty;
       9:         this.SampleRate = 44100;
      10:         this.Grammars = new String[0];
      11:         this.Choices = new String[0];
      12:     }
      13:  
      14:     public event EventHandler<SpeechRecognizedEventArgs> SpeechRecognized;
      15:  
      16:     [DefaultValue("")]
      17:     public String Culture
      18:     {
      19:         get;
      20:         set;
      21:     }
      22:  
      23:     [DefaultValue(SpeechRecognitionMode.Desktop)]
      24:     public SpeechRecognitionMode Mode
      25:     {
      26:         get;
      27:         set;
      28:     }
      29:  
      30:     [DefaultValue("")]
      31:     public String OnClientSpeechRecognized
      32:     {
      33:         get;
      34:         set;
      35:     }
      36:  
      37:     [DefaultValue(44100)]
      38:     public UInt32 SampleRate
      39:     {
      40:         get;
      41:         set;
      42:     }
      43:  
      44:     [TypeConverter(typeof(StringArrayConverter))]
      45:     [DefaultValue("")]
      46:     public String [] Grammars
      47:     {
      48:         get;
      49:         private set;
      50:     }
      51:  
      52:     [TypeConverter(typeof(StringArrayConverter))]
      53:     [DefaultValue("")]
      54:     public String[] Choices
      55:     {
      56:         get;
      57:         set;
      58:     }
      59:  
      60:     protected override void OnInit(EventArgs e)
      61:     {
      62:         if (this.Page.Items.Contains(typeof(SpeechRecognition)))
      63:         {
      64:             throw (new InvalidOperationException("There can be only one SpeechRecognition control on a page."));
      65:         }
      66:  
      67:         var sm = ScriptManager.GetCurrent(this.Page);
      68:         var reference = this.Page.ClientScript.GetCallbackEventReference(this, "sound", String.Format("function(result){{ {0}(JSON.parse(result)); }}", String.IsNullOrWhiteSpace(this.OnClientSpeechRecognized) ? "void" : this.OnClientSpeechRecognized), String.Empty, true);
      69:         var script = String.Format("\nvar processor = document.getElementById('{0}'); processor.stopRecording = function(sampleRate) {{ window.stopRecording(processor, sampleRate ? sampleRate : 44100); }}; processor.startRecording = function() {{ window.startRecording(); }}; processor.process = function(sound){{ {1} }};\n", this.ClientID, reference);
      70:  
      71:         if (sm != null)
      72:         {
      73:             this.Page.ClientScript.RegisterStartupScript(this.GetType(), String.Concat("process", this.ClientID), String.Format("Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function() {{ {0} }});\n", script), true);
      74:         }
      75:         else
      76:         {
      77:             this.Page.ClientScript.RegisterStartupScript(this.GetType(), String.Concat("process", this.ClientID), script, true);
      78:         }
      79:  
      80:         this.Page.ClientScript.RegisterClientScriptResource(this.GetType(), String.Concat(this.GetType().Namespace, ".Script.js"));
      81:         this.Page.Items[typeof(SpeechRecognition)] = this;
      82:  
      83:         base.OnInit(e);
      84:     }
      85:  
      86:     protected virtual void OnSpeechRecognized(SpeechRecognizedEventArgs e)
      87:     {
      88:         var handler = this.SpeechRecognized;
      89:  
      90:         if (handler != null)
      91:         {
      92:             handler(this, e);
      93:         }
      94:     }
      95:  
      96:     protected SpeechRecognitionProvider GetProvider()
      97:     {
      98:         switch (this.Mode)
      99:         {
     100:             case SpeechRecognitionMode.Desktop:
     101:                 return (new DesktopSpeechRecognitionProvider(this));
     102:  
     103:             case SpeechRecognitionMode.Server:
     104:                 return (new ServerSpeechRecognitionProvider(this));
     105:         }
     106:  
     107:         return (null);
     108:     }
     109:  
     110:     #region ICallbackEventHandler Members
     111:  
     112:     String ICallbackEventHandler.GetCallbackResult()
     113:     {
     114:         AsyncOperationManager.SynchronizationContext = new SynchronizationContext();
     115:  
     116:         var filename = Path.GetTempFileName();
     117:         var result = null as SpeechRecognitionResult;
     118:  
     119:         using (var engine = this.GetProvider())
     120:         {
     121:             var data = this.Context.Items["data"].ToString();
     122:  
     123:             using (var file = File.OpenWrite(filename))
     124:             {
     125:                 var bytes = Convert.FromBase64String(data);
     126:                 file.Write(bytes, 0, bytes.Length);
     127:             }
     128:  
     129:             result = engine.RecognizeFromWav(filename) ?? new SpeechRecognitionResult(String.Empty);
     130:         }
     131:  
     132:         File.Delete(filename);
     133:  
     134:         var args = new SpeechRecognizedEventArgs(result);
     135:  
     136:         this.OnSpeechRecognized(args);
     137:  
     138:         var json = new JavaScriptSerializer().Serialize(result);
     139:  
     140:         return (json);
     141:     }
     142:  
     143:     void ICallbackEventHandler.RaiseCallbackEvent(String eventArgument)
     144:     {
     145:         this.Context.Items["data"] = eventArgument;
     146:     }
     147:  
     148:     #endregion
     149: }

    SpeechRecognition implements ICallbackEventHandler for a self-contained AJAX experience; it registers a couple of JavaScript functions and also an embedded JavaScript file, for some useful sound manipulation and conversion. Only one instance is allowed on a page. On the client-side, this JavaScript uses getUserMedia to access an audio source and uses a clever mechanism to pack them as a .WAV file in a Data URI. I got these functions from http://typedarray.org/from-microphone-to-wav-with-getusermedia-and-web-audio/ and made some changes to them. I like them because they don’t require any external library, which makes all this pretty much self-contained.

    The control exposes some custom properties:

    • Culture: an optional culture name, such as “pt-PT” or “en-US”; if not specified, it defaults to the current culture in the server machine;
    • Mode: one of the two providers: Desktop (for System.Speech) or Server (for Microsoft.Speech, of Microsoft Speech Platform);
    • OnClientSpeechRecognized: the name of a callback JavaScript version that will be called when there are results (more on this later);
    • SampleRate: a sample rate, by default, it is 44100;
    • Grammars: an optional collection of additional grammar files, with extension .grxml (Speech Recognition Grammar Specification), to add to the engine;
    • Choices: an optional collection of choices to recognize, if we want to restrict the scope, such as “yes”/”no”, “red”/”green”, etc.

    The mode enumeration looks like this:

       1: public enum SpeechRecognitionMode
       2: {
       3:     Desktop,
       4:     Server
       5: }

    The SpeechRecognition control also has an event, SpeechRecognized, which allows overriding the detected phrases. Its argument is this simple class that follows the regular .NET event pattern:

       1: [Serializable]
       2: public sealed class SpeechRecognizedEventArgs : EventArgs
       3: {
       4:     public SpeechRecognizedEventArgs(SpeechRecognitionResult result)
       5:     {
       6:         this.Result = result;
       7:     }
       8:  
       9:     public SpeechRecognitionResult Result
      10:     {
      11:         get;
      12:         private set;
      13:     }
      14: }

    Which in turn holds a SpeechRecognitionResult:

       1: public class SpeechRecognitionResult
       2: {
       3:     public SpeechRecognitionResult(String text, params String [] alternates)
       4:     {
       5:         this.Text = text;
       6:         this.Alternates = alternates.ToList();
       7:     }
       8:  
       9:     public String Text
      10:     {
      11:         get;
      12:         set;
      13:     }
      14:  
      15:     public List<String> Alternates
      16:     {
      17:         get;
      18:         private set;
      19:     }
      20: }

    This class receives the phrase that the speech recognition engine understood plus an array of additional alternatives, in descending order.

    The JavaScript file containing the utility functions is embedded in the assembly:

    image

    You need to add an assembly-level attribute, WebResourceAttribute, possibly in AssemblyInfo.cs, of course, replacing MyNamespace for your assembly’s default namespace:

       1: [assembly: WebResource("MyNamespace.Script.js", "text/javascript")]

    This attribute registers a script file with some content-type so that it can be included in a page by the RegisterClientScriptResource method.

    And here is it:

       1: // variables
       2: var leftchannel = [];
       3: var rightchannel = [];
       4: var recorder = null;
       5: var recording = false;
       6: var recordingLength = 0;
       7: var volume = null;
       8: var audioInput = null;
       9: var audioContext = null;
      10: var context = null;
      11:  
      12: // feature detection 
      13: navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
      14:  
      15: if (navigator.getUserMedia)
      16: {
      17:     navigator.getUserMedia({ audio: true }, onSuccess, onFailure);
      18: }
      19: else
      20: {
      21:     alert('getUserMedia not supported in this browser.');
      22: }
      23:  
      24: function startRecording()
      25: {
      26:     recording = true;
      27:     // reset the buffers for the new recording
      28:     leftchannel.length = rightchannel.length = 0;
      29:     recordingLength = 0;
      30:     leftchannel = [];
      31:     rightchannel = [];
      32: }
      33:  
      34: function stopRecording(elm, sampleRate)
      35: {
      36:     recording = false;
      37:  
      38:     // we flat the left and right channels down
      39:     var leftBuffer = mergeBuffers(leftchannel, recordingLength);
      40:     var rightBuffer = mergeBuffers(rightchannel, recordingLength);
      41:     // we interleave both channels together
      42:     var interleaved = interleave(leftBuffer, rightBuffer);
      43:  
      44:     // we create our wav file
      45:     var buffer = new ArrayBuffer(44 + interleaved.length * 2);
      46:     var view = new DataView(buffer);
      47:  
      48:     // RIFF chunk descriptor
      49:     writeUTFBytes(view, 0, 'RIFF');
      50:     view.setUint32(4, 44 + interleaved.length * 2, true);
      51:     writeUTFBytes(view, 8, 'WAVE');
      52:     // FMT sub-chunk
      53:     writeUTFBytes(view, 12, 'fmt ');
      54:     view.setUint32(16, 16, true);
      55:     view.setUint16(20, 1, true);
      56:     // stereo (2 channels)
      57:     view.setUint16(22, 2, true);
      58:     view.setUint32(24, sampleRate, true);
      59:     view.setUint32(28, sampleRate * 4, true);
      60:     view.setUint16(32, 4, true);
      61:     view.setUint16(34, 16, true);
      62:     // data sub-chunk
      63:     writeUTFBytes(view, 36, 'data');
      64:     view.setUint32(40, interleaved.length * 2, true);
      65:  
      66:     // write the PCM samples
      67:     var index = 44;
      68:     var volume = 1;
      69:  
      70:     for (var i = 0; i < interleaved.length; i++)
      71:     {
      72:         view.setInt16(index, interleaved[i] * (0x7FFF * volume), true);
      73:         index += 2;
      74:     }
      75:  
      76:     // our final binary blob
      77:     var blob = new Blob([view], { type: 'audio/wav' });
      78:  
      79:     var reader = new FileReader();
      80:     reader.onloadend = function ()
      81:     {
      82:         var url = reader.result.replace('data:audio/wav;base64,', '');
      83:         elm.process(url);
      84:     };
      85:     reader.readAsDataURL(blob);
      86: }
      87:  
      88: function interleave(leftChannel, rightChannel)
      89: {
      90:     var length = leftChannel.length + rightChannel.length;
      91:     var result = new Float32Array(length);
      92:     var inputIndex = 0;
      93:  
      94:     for (var index = 0; index < length;)
      95:     {
      96:         result[index++] = leftChannel[inputIndex];
      97:         result[index++] = rightChannel[inputIndex];
      98:         inputIndex++;
      99:     }
     100:  
     101:     return result;
     102: }
     103:  
     104: function mergeBuffers(channelBuffer, recordingLength)
     105: {
     106:     var result = new Float32Array(recordingLength);
     107:     var offset = 0;
     108:  
     109:     for (var i = 0; i < channelBuffer.length; i++)
     110:     {
     111:         var buffer = channelBuffer[i];
     112:         result.set(buffer, offset);
     113:         offset += buffer.length;
     114:     }
     115:  
     116:     return result;
     117: }
     118:  
     119: function writeUTFBytes(view, offset, string)
     120: {
     121:     for (var i = 0; i < string.length; i++)
     122:     {
     123:         view.setUint8(offset + i, string.charCodeAt(i));
     124:     }
     125: }
     126:  
     127: function onFailure(e)
     128: {
     129:     alert('Error capturing audio.');
     130: }
     131:  
     132: function onSuccess(e)
     133: {
     134:     // creates the audio context
     135:     audioContext = (window.AudioContext || window.webkitAudioContext);
     136:     context = new audioContext();
     137:  
     138:     // creates a gain node
     139:     volume = context.createGain();
     140:  
     141:     // creates an audio node from the microphone incoming stream
     142:     audioInput = context.createMediaStreamSource(e);
     143:  
     144:     // connect the stream to the gain node
     145:     audioInput.connect(volume);
     146:  
     147:     /* From the spec: This value controls how frequently the audioprocess event is 
     148:     dispatched and how many sample-frames need to be processed each call. 
     149:     Lower values for buffer size will result in a lower (better) latency. 
     150:     Higher values will be necessary to avoid audio breakup and glitches */
     151:     var bufferSize = 2048;
     152:  
     153:     recorder = context.createScriptProcessor(bufferSize, 2, 2);
     154:     recorder.onaudioprocess = function (e)
     155:     {
     156:         if (recording == false)
     157:         {
     158:             return;
     159:         }
     160:  
     161:         var left = e.inputBuffer.getChannelData(0);
     162:         var right = e.inputBuffer.getChannelData(1);
     163:  
     164:         // we clone the samples
     165:         leftchannel.push(new Float32Array(left));
     166:         rightchannel.push(new Float32Array(right));
     167:  
     168:         recordingLength += bufferSize;
     169:     }
     170:  
     171:     // we connect the recorder
     172:     volume.connect(recorder);
     173:     recorder.connect(context.destination);
     174: }

    OK, let’s move on the the provider implementations; first, Desktop:

       1: public class DesktopSpeechRecognitionProvider : SpeechRecognitionProvider
       2: {
       3:     public DesktopSpeechRecognitionProvider(SpeechRecognition recognition) : base(recognition)
       4:     {
       5:     }
       6:  
       7:     public override SpeechRecognitionResult RecognizeFromWav(String filename)
       8:     {
       9:         var engine = null as SpeechRecognitionEngine;
      10:  
      11:         if (String.IsNullOrWhiteSpace(this.Recognition.Culture) == true)
      12:         {
      13:             engine = new SpeechRecognitionEngine();
      14:         }
      15:         else
      16:         {
      17:             engine = new SpeechRecognitionEngine(CultureInfo.CreateSpecificCulture(this.Recognition.Culture));
      18:         }
      19:  
      20:         using (engine)
      21:         {
      22:             if ((this.Recognition.Grammars.Any() == false) && (this.Recognition.Choices.Any() == false))
      23:             {
      24:                 engine.LoadGrammar(new DictationGrammar());
      25:             }
      26:  
      27:             foreach (var grammar in this.Recognition.Grammars)
      28:             {
      29:                 var doc = new SrgsDocument(Path.Combine(HttpRuntime.AppDomainAppPath, grammar));
      30:                 engine.LoadGrammar(new Grammar(doc));
      31:             }
      32:  
      33:             if (this.Recognition.Choices.Any() == true)
      34:             {
      35:                 var choices = new Choices(this.Recognition.Choices.ToArray());
      36:                 engine.LoadGrammar(new Grammar(choices));
      37:             }
      38:  
      39:             engine.SetInputToWaveFile(filename);
      40:  
      41:             var result = engine.Recognize();
      42:  
      43:             return ((result != null) ? new SpeechRecognitionResult(result.Text, result.Alternates.Select(x => x.Text).ToArray()) : null);
      44:         }
      45:     }
      46: }

    What this provider does is simply receive the location of a .WAV file and feed it to SpeechRecognitionEngine, together with some parameters of SpeechRecognition (Culture, AudioRate, Grammars and Choices)

    Finally, the code for the Server (Microsoft Speech Platform Software Development Kit) version:

       1: public class ServerSpeechRecognitionProvider : SpeechRecognitionProvider
       2: {
       3:     public ServerSpeechRecognitionProvider(SpeechRecognition recognition) : base(recognition)
       4:     {
       5:     }
       6:  
       7:     public override SpeechRecognitionResult RecognizeFromWav(String filename)
       8:     {
       9:         var engine = null as SpeechRecognitionEngine;
      10:  
      11:         if (String.IsNullOrWhiteSpace(this.Recognition.Culture) == true)
      12:         {
      13:             engine = new SpeechRecognitionEngine();
      14:         }
      15:         else
      16:         {
      17:             engine = new SpeechRecognitionEngine(CultureInfo.CreateSpecificCulture(this.Recognition.Culture));
      18:         }
      19:  
      20:         using (engine)
      21:         {
      22:             foreach (var grammar in this.Recognition.Grammars)
      23:             {
      24:                 var doc = new SrgsDocument(Path.Combine(HttpRuntime.AppDomainAppPath, grammar));
      25:                 engine.LoadGrammar(new Grammar(doc));
      26:             }
      27:  
      28:             if (this.Recognition.Choices.Any() == true)
      29:             {
      30:                 var choices = new Choices(this.Recognition.Choices.ToArray());
      31:                 engine.LoadGrammar(new Grammar(choices));
      32:             }
      33:  
      34:             engine.SetInputToWaveFile(filename);
      35:  
      36:             var result = engine.Recognize();
      37:  
      38:             return ((result != null) ? new SpeechRecognitionResult(result.Text, result.Alternates.Select(x => x.Text).ToArray()) : null);
      39:         }
      40:     }
      41: }

    As you can see, it is very similar to the Desktop one. Keep in mind, however, that for this provider to work you will have to download the Microsoft Speech Platform SDK, the Microsoft Speech Platform Runtime and at least one language from the Language Pack.

    Here is a sample markup declaration:

       1: <web:SpeechRecognition runat="server" ID="processor" ClientIDMode="Static" Mode="Desktop" Culture="en-US" OnSpeechRecognized="OnSpeechRecognized" OnClientSpeechRecognized="onSpeechRecognized" />

    If you want to add specific choices, add the Choices attribute to the control declaration and separate the values by commas:

       1: Choices="One, Two, Three"

    Or add a grammar file:

       1: Grammars="~/MyGrammar.grxml"

    By the way, grammars are not so difficult to create, you can find a good documentation in MSDN: http://msdn.microsoft.com/en-us/library/ee800145.aspx.

    To finalize, a sample JavaScript for starting recognition and receiving the results:

       1: <script type="text/javascript">
       1:  
       2:     
       3:     function onSpeechRecognized(result)
       4:     {
       5:         window.alert('Recognized: ' + result.Text + '\nAlternatives: ' + String.join(', ', result.Alternatives));
       6:     }
       7:  
       8:     function start()
       9:     {
      10:         document.getElementById('processor').startRecording();
      11:     }
      12:  
      13:     function stop()
      14:     {
      15:         document.getElementById('processor').stopRecording();
      16:     }
      17:  
    </script>

    And that’s it. You start recognition by calling startRecording(), get results in onSpeechRecognized() (or any other function set in the OnClientSpeechRecognized property) and stop recording with stopRecording(). The values passed to onSpeechRecognized() are those that may have been filtered by the server-side SpeechRecognized event handler.

    A final word of advisory: because generated sound files may become very large, do keep the recordings as short as possible.

    Of course, this offers several different possibilities, I am looking forward to hearing them from you! Winking smile

    Read more...

  • ASP.NET NHibernateDataSource

    A long, long time ago, I wrote a NHibernateDataSource control. Back then, it was based in the first LINQ provider for NHibernate, and a long has happened since. Now, I decided to give it another go! Smile

    Historically, in ASP.NET, a data control should inherit from DataSourceControl, like ObjectDataSource, LinqDataSource, EntityDataSource, SqlDataSource, etc, and should expose collections for parameters for each of the supported operations (select, update, delete and insert). Since ASP.NET 4, however, a new base class came along: QueryableDataSource. This class is an implementation of IQueryableDataSource, which allows using QueryExtender, also introduced in version 4, to filter and sort the results of a data source that uses LINQ.

    I wanted my control to be able to use QueryExtender, but I also wanted to be able to give it an HQL query. It should also be capable of inserting, updating and deleting entities.

    So, here’s what I came up with, first, the NHibernateDataSource class:

       1: [ParseChildren(true)]
       2: public class NHibernateDataSource : QueryableDataSource
       3: {
       4:     public NHibernateDataSource()
       5:     {
       6:         this.SelectParameters = new ParameterCollection();
       7:         this.InsertParameters = new ParameterCollection();
       8:         this.UpdateParameters = new ParameterCollection();
       9:         this.DeleteParameters = new ParameterCollection();
      10:     }
      11:  
      12:     [Description("Raised when a session factory is built")]
      13:     public event EventHandler<BuildSessionFactoryEventArgs> BuildSessionFactory;
      14:     [Description("Raised when a configuration instance is created")]
      15:     public event EventHandler<ConfigureEventArgs> Configure;
      16:     [Description("Raised when an entity is created for inserts or updates")]
      17:     public event EventHandler<CreateInstanceEventArgs> CreateInstance;
      18:  
      19:     [Description("Raised after an entity is inserted")]
      20:     public event EventHandler<EntityEventArgs> EntityInserted;
      21:     [Description("Raised after an entity is deleted")]
      22:     public event EventHandler<EntityEventArgs> EntityDeleted;
      23:     [Description("Raised after an entity is updated")]
      24:     public event EventHandler<EntityEventArgs> EntityUpdated;
      25:     [Description("Raised after a query is executed")]
      26:     public event EventHandler<EntitiesSelectedEventArgs> EntitiesSelected;
      27:     [Description("Raised when an operation completes (select, insert, update or delete)")]
      28:     public event EventHandler<OperationCompletedEventArgs> OperationCompleted;
      29:  
      30:     [Description("Raised before a select is made")]
      31:     public event EventHandler<EntitiesSelectingEventArgs> EntitiesSelecting;
      32:     [Description("Raised before an entity is inserted")]
      33:     public event EventHandler<EntityEventArgs> EntityInserting;
      34:     [Description("Raised before an entity is deleted")]
      35:     public event EventHandler<EntityEventArgs> EntityDeleting;
      36:     [Description("Raised before an entity is updated")]
      37:     public event EventHandler<EntityEventArgs> EntityUpdating;
      38:  
      39:     [Description("The entity name to update, delete or insert")]
      40:     [DefaultValue("")]
      41:     public String EntityName
      42:     {
      43:         get;
      44:         set;
      45:     }
      46:  
      47:     [Description("The HQL to use for selecting records, when mode Hql is selected")]
      48:     [DefaultValue("")]
      49:     public String Hql
      50:     {
      51:         get;
      52:         set;
      53:     }
      54:  
      55:     [Description("The maximum number of records to retrieve, if paging is not used")]
      56:     [DefaultValue(0)]
      57:     public Int32 MaximumRecords
      58:     {
      59:         get;
      60:         set;
      61:     }
      62:  
      63:     [Description("The page size to retrieve")]
      64:     [DefaultValue(0)]
      65:     public Int32 PageSize
      66:     {
      67:         get;
      68:         set;
      69:     }
      70:  
      71:     [Description("The page index to retrieve")]
      72:     [DefaultValue(0)]
      73:     public Int32 PageIndex
      74:     {
      75:         get;
      76:         set;
      77:     }
      78:  
      79:     [Description("Use HQL or EntityName for selecting")]
      80:     [DefaultValue(NHibernateDataSourceMode.Linq)]
      81:     public NHibernateDataSourceMode Mode
      82:     {
      83:         get;
      84:         set;
      85:     }
      86:  
      87:     [Description("Whether to merge the entity before updating or deleting or not")]
      88:     [DefaultValue(false)]
      89:     public Boolean RefreshBeforeUpdate
      90:     {
      91:         get;
      92:         set;
      93:     }
      94:  
      95:     [Description("Parameters that will be used for the HQL query")]
      96:     public ParameterCollection SelectParameters
      97:     {
      98:         get;
      99:         private set;
     100:     }
     101:  
     102:     [Description("Parameters that will be used for inserting a new entity")]
     103:     public ParameterCollection InsertParameters
     104:     {
     105:         get;
     106:         private set;
     107:     }
     108:  
     109:     [Description("Parameters that will be used for updating an existing entity")]
     110:     public ParameterCollection UpdateParameters
     111:     {
     112:         get;
     113:         private set;
     114:     }
     115:  
     116:     [Description("Parameters that will be used for deleting an existing entity")]
     117:     public ParameterCollection DeleteParameters
     118:     {
     119:         get;
     120:         private set;
     121:     }
     122:  
     123:     [Browsable(false)]
     124:     public ISessionFactory SessionFactory
     125:     {
     126:         get;
     127:         set;
     128:     }
     129:  
     130:     internal static ISessionFactory InternalSessionFactory
     131:     {
     132:         get;
     133:         set;
     134:     }
     135:  
     136:     internal ISessionFactory EffectiveSessionFactory
     137:     {
     138:         get
     139:         {
     140:             var sessionFactory = this.SessionFactory ?? InternalSessionFactory;
     141:             var sfArgs = new BuildSessionFactoryEventArgs() { SessionFactory = sessionFactory };
     142:  
     143:             this.OnBuildSessionFactory(sfArgs);
     144:  
     145:             if (sfArgs.SessionFactory == null)
     146:             {
     147:                 var cfg = new Configuration().Configure();
     148:  
     149:                 var cfgArgs = new ConfigureEventArgs() { Configuration =  cfg };
     150:  
     151:                 this.OnConfigure(cfgArgs);
     152:  
     153:                 cfg = cfgArgs.Configuration;
     154:  
     155:                 sessionFactory = cfg.BuildSessionFactory();
     156:  
     157:                 if (InternalSessionFactory == null)
     158:                 {
     159:                     InternalSessionFactory = sessionFactory;
     160:                 }
     161:             }
     162:             else
     163:             {
     164:                 sessionFactory = sfArgs.SessionFactory;
     165:             }
     166:  
     167:             return (sessionFactory);
     168:         }
     169:     }
     170:  
     171:     protected virtual void OnBuildSessionFactory(BuildSessionFactoryEventArgs e)
     172:     {
     173:         var handler = this.BuildSessionFactory;
     174:  
     175:         if (handler != null)
     176:         {
     177:             handler(this, e);
     178:         }
     179:     }
     180:  
     181:     protected virtual void OnConfigure(ConfigureEventArgs e)
     182:     {
     183:         var handler = this.Configure;
     184:  
     185:         if (handler != null)
     186:         {
     187:             handler(this, e);
     188:         }
     189:     }
     190:  
     191:     protected virtual void OnCreateInstance(CreateInstanceEventArgs e)
     192:     {
     193:         var handler = this.CreateInstance;
     194:  
     195:         if (handler != null)
     196:         {
     197:             handler(this, e);
     198:         }
     199:     }
     200:  
     201:     protected virtual void OnEntitiesSelecting(EntitiesSelectingEventArgs e)
     202:     {
     203:         var handler = this.EntitiesSelecting;
     204:  
     205:         if (handler != null)
     206:         {
     207:             handler(this, e);
     208:         }
     209:     }
     210:  
     211:     protected virtual void OnEntityInserted(EntityEventArgs e)
     212:     {
     213:         var handler = this.EntityInserted;
     214:  
     215:         if (handler != null)
     216:         {
     217:             handler(this, e);
     218:         }
     219:     }
     220:  
     221:     protected virtual void OnEntityDeleted(EntityEventArgs e)
     222:     {
     223:         var handler = this.EntityDeleted;
     224:  
     225:         if (handler != null)
     226:         {
     227:             handler(this, e);
     228:         }
     229:     }
     230:  
     231:     protected virtual void OnEntityUpdated(EntityEventArgs e)
     232:     {
     233:         var handler = this.EntityUpdated;
     234:  
     235:         if (handler != null)
     236:         {
     237:             handler(this, e);
     238:         }
     239:     }
     240:  
     241:     protected virtual void OnEntityInserting(EntityEventArgs e)
     242:     {
     243:         var handler = this.EntityInserting;
     244:  
     245:         if (handler != null)
     246:         {
     247:             handler(this, e);
     248:         }
     249:     }
     250:  
     251:     protected virtual void OnEntityDeleting(EntityEventArgs e)
     252:     {
     253:         var handler = this.EntityDeleting;
     254:  
     255:         if (handler != null)
     256:         {
     257:             handler(this, e);
     258:         }
     259:     }
     260:  
     261:     protected virtual void OnEntityUpdating(EntityEventArgs e)
     262:     {
     263:         var handler = this.EntityUpdating;
     264:  
     265:         if (handler != null)
     266:         {
     267:             handler(this, e);
     268:         }
     269:     }
     270:  
     271:     public virtual void OnEntitiesSelected(EntitiesSelectedEventArgs e)
     272:     {
     273:         var handler = this.EntitiesSelected;
     274:  
     275:         if (handler != null)
     276:         {
     277:             handler(this, e);
     278:         }
     279:     }
     280:  
     281:     public virtual void OnOperationCompleted(OperationCompletedEventArgs e)
     282:     {
     283:         var handler = this.OperationCompleted;
     284:  
     285:         if (handler != null)
     286:         {
     287:             handler(this, e);
     288:         }
     289:     }
     290:  
     291:     public Int32 Insert()
     292:     {
     293:         if (String.IsNullOrWhiteSpace(this.EntityName) == true)
     294:         {
     295:             throw (new InvalidOperationException("The EntityName property cannot be empty."));
     296:         }
     297:  
     298:         if (this.InsertParameters.Count == 0)
     299:         {
     300:             throw (new InvalidOperationException("Missing InsertParameters."));
     301:         }
     302:  
     303:         using (var session = this.EffectiveSessionFactory.OpenStatelessSession())
     304:         using (session.BeginTransaction())
     305:         {
     306:             var args = new EntityEventArgs(this.CreateInstanceAndSetParameters(this.InsertParameters));
     307:  
     308:             this.OnEntityInserting(args);
     309:  
     310:             if (args.Cancel == true)
     311:             {
     312:                 return (0);
     313:             }
     314:  
     315:             session.Insert(args.Entity);
     316:             session.Transaction.Commit();
     317:  
     318:             this.OnEntityInserted(args);
     319:             this.OnOperationCompleted(new OperationCompletedEventArgs(DataSourceOperation.Insert, args.Entity));
     320:         }
     321:  
     322:         return (1);
     323:     }
     324:  
     325:     public Int32 Update()
     326:     {
     327:         if (String.IsNullOrWhiteSpace(this.EntityName) == true)
     328:         {
     329:             throw (new InvalidOperationException("The EntityName property cannot be empty."));
     330:         }
     331:  
     332:         if (this.UpdateParameters.Count == 0)
     333:         {
     334:             throw (new InvalidOperationException("Missing UpdateParameters."));
     335:         }
     336:  
     337:         using (var session = this.EffectiveSessionFactory.OpenStatelessSession())
     338:         using (session.BeginTransaction())
     339:         {
     340:             var args = new EntityEventArgs(this.CreateInstanceAndSetParameters(this.UpdateParameters));
     341:  
     342:             this.OnEntityUpdating(args);
     343:  
     344:             if (args.Cancel == true)
     345:             {
     346:                 return (0);
     347:             }
     348:  
     349:             if (this.RefreshBeforeUpdate == true)
     350:             {
     351:                 this.Refresh(args.Entity);
     352:             }
     353:  
     354:             session.Update(args.Entity);
     355:             session.Transaction.Commit();
     356:  
     357:             this.OnEntityUpdated(args);
     358:             this.OnOperationCompleted(new OperationCompletedEventArgs(DataSourceOperation.Update, args.Entity));
     359:  
     360:             return (1);
     361:         }
     362:     }
     363:  
     364:     public Int32 Delete()
     365:     {
     366:         if (String.IsNullOrWhiteSpace(this.EntityName) == true)
     367:         {
     368:             throw (new InvalidOperationException("The EntityName property cannot be empty."));
     369:         }
     370:  
     371:         using (var session = this.EffectiveSessionFactory.OpenStatelessSession())
     372:         using (session.BeginTransaction())
     373:         {
     374:             var args = new EntityEventArgs(this.CreateInstanceAndSetParameters(this.DeleteParameters));
     375:  
     376:             this.OnEntityDeleting(args);
     377:  
     378:             if (args.Cancel == true)
     379:             {
     380:                 return (0);
     381:             }
     382:  
     383:             if (this.RefreshBeforeUpdate == true)
     384:             {
     385:                 this.Refresh(args.Entity);
     386:             }
     387:  
     388:             session.Delete(args.Entity);
     389:             session.Transaction.Commit();
     390:  
     391:             this.OnEntityDeleted(args);
     392:             this.OnOperationCompleted(new OperationCompletedEventArgs(DataSourceOperation.Delete, args.Entity));
     393:  
     394:             return (1);
     395:         }
     396:     }
     397:  
     398:     protected void Refresh(Object entity)
     399:     {
     400:         using (var session = this.EffectiveSessionFactory.OpenSession())
     401:         {
     402:             session.DefaultReadOnly = true;
     403:             session.FlushMode = FlushMode.Never;
     404:  
     405:             var metadata = this.GetMetadata(this.EntityName);
     406:             var propertiesToLoad = new List<String>();
     407:  
     408:             for (var i = 0; i < metadata.PropertyNames.Length; ++i)
     409:             {
     410:                 if (metadata.GetPropertyValue(entity, metadata.PropertyNames[i], EntityMode.Poco) == null)
     411:                 {
     412:                     if (metadata.PropertyTypes[i].IsEntityType == false)
     413:                     {
     414:                         propertiesToLoad.Add(metadata.PropertyNames[i]);
     415:                     }
     416:                     else
     417:                     {
     418:                         propertiesToLoad.Add(String.Concat(metadata.PropertyNames[i], ".id"));
     419:                     }
     420:                 }
     421:             }
     422:  
     423:             var hql = new StringBuilder();
     424:             hql.Append("select ");
     425:             hql.Append(String.Join(", ", propertiesToLoad));
     426:             hql.AppendFormat(" from {0} where id = :id", entity.GetType().FullName);
     427:  
     428:             var query = session.CreateQuery(hql.ToString());
     429:             query.SetParameter("id", metadata.GetIdentifier(entity, EntityMode.Poco));
     430:  
     431:             var result = query.UniqueResult();
     432:             var values = (result as Object[]) ?? new Object[] { result };
     433:  
     434:             for (var i = 0; i < propertiesToLoad.Count; ++i)
     435:             {
     436:                 var parts = propertiesToLoad[i].Split('.');
     437:                 var value = values[i];
     438:                 var propertyName = parts.First();
     439:  
     440:                 if (parts.Length > 1)
     441:                 {
     442:                     var propertyIndex = Array.IndexOf(metadata.PropertyNames, propertyName);
     443:                     var propertyType = metadata.PropertyTypes[propertyIndex].ReturnedClass;
     444:  
     445:                     value = session.Load(propertyType, values[i]);
     446:                 }
     447:  
     448:                 metadata.SetPropertyValue(entity, propertyName, value, EntityMode.Poco);
     449:             }
     450:         }
     451:     }
     452:  
     453:     protected internal IDictionary<String, Object> GetParameters(ParameterCollection parameters)
     454:     {
     455:         return (parameters.GetValues(this.Context, this).OfType<DictionaryEntry>().ToDictionary(x => x.Key.ToString(), x => x.Value));
     456:     }
     457:  
     458:     protected void SetParameterValues(Object instance, IClassMetadata metadata, IDictionary<String, Object> parameters)
     459:     {
     460:         foreach (var parameter in parameters)
     461:         {
     462:             if (metadata.PropertyNames.Contains(parameter.Key) == true)
     463:             {
     464:                 metadata.SetPropertyValue(instance, parameter.Key, parameter.Value, EntityMode.Poco);
     465:             }
     466:             else if (metadata.IdentifierPropertyName == parameter.Key)
     467:             {
     468:                 metadata.SetIdentifier(instance, parameter.Value, EntityMode.Poco);
     469:             }
     470:         }
     471:     }
     472:  
     473:     protected Object CreateInstanceAndSetParameters(ParameterCollection parameters)
     474:     {
     475:         var metadata = this.GetMetadata(this.EntityName);
     476:  
     477:         if (metadata == null)
     478:         {
     479:             throw (new InvalidOperationException("Entity could not be found."));
     480:         }
     481:  
     482:         var entityType = metadata.GetMappedClass(EntityMode.Poco);
     483:  
     484:         var ciArgs = new CreateInstanceEventArgs(entityType, null);
     485:  
     486:         this.OnCreateInstance(ciArgs);
     487:  
     488:         if (ciArgs.Instance == null)
     489:         {
     490:             ciArgs.Instance = Activator.CreateInstance(entityType);
     491:         }
     492:  
     493:         this.SetParameterValues(ciArgs.Instance, metadata, this.GetParameters(parameters));
     494:  
     495:         return (ciArgs.Instance);
     496:     }
     497:  
     498:     protected internal IClassMetadata GetMetadata(String entityName)
     499:     {
     500:         var metadata = this.EffectiveSessionFactory.GetAllClassMetadata().Where(x => x.Key.EndsWith(entityName)).Select(x => x.Value).SingleOrDefault();
     501:  
     502:         return (metadata);
     503:     }
     504:  
     505:     protected internal void ProcessEntitiesSelecting(EntitiesSelectingEventArgs e)
     506:     {
     507:         this.OnEntitiesSelecting(e);
     508:     }
     509:  
     510:     protected internal void ProcessEntitiesSelected(EntitiesSelectedEventArgs e)
     511:     {
     512:         this.OnEntitiesSelected(e);
     513:         this.OnOperationCompleted(new OperationCompletedEventArgs(DataSourceOperation.Select, e.Results));
     514:     }
     515:  
     516:     protected override QueryableDataSourceView CreateQueryableView()
     517:     {
     518:         return (new NHibernateDataSourceView(this, "DefaultView", this.Context) as QueryableDataSourceView);
     519:     }
     520: }

    You can see that it exposes some events:

    • Configure: gives developers a chance to build (or return an existing) Configuration instance, that will be used for building the session factory;
    • BuildSessionFactory: allows setting parameters on the default session factory or returning an existing one;
    • CreateInstance: raised before NHibernate creates a default instance, to allow developers to return one;
    • EntityInserting: raised before an entity is inserted, allowing developers to cancel the operations or to set entity parameter;
    • EntityUpdating: raised before an entity is updated, allowing developers to cancel the operations or to set entity parameter;
    • EntityDeleting: raised before an entity is deleting, allowing its cancellation;
    • EntitiesSelecting: raised before a select operation is performed;
    • EntityInserted: raised after an entity was inserted;
    • EntityUpdated: raised after an entity was updated;
    • EntityDeleted: raised after an entity was deleted;
    • EntitiesSelected: raised after a select operation was performed;
    • OperationCompleted: raised after an operation completes (select, insert, update or delete).


    If no handler for CreateInstance is supplied, NHibernateDataSource will try to create an entity using Activator.CreateInstance.

    EntitySelecting is raised regardless of the Mode (Hql or Linq), but it will have different values in its argument: a query string plus parameters in the case of Hql and an IQueryable instance for Linq.

    EntityInserting, EntityUpdating and EntityDeleting allow the modification of properties of the entity in the parameter, but not the replacing of the entity itself.

    OperationCompleted is always called, except in the event of an exception.

    It also exposes a couple of properties:

    • Mode: one of the two operation modes, Hql or Linq. If Hql is used, then the Hql property must be set; otherwise, it’s EntityName that is required;
    • Hql: an NHibernate HQL query string;
    • EntityName: the name of an entity that the control will work with; only required for Mode Linq or for inserts, updates or deletes;
    • RefreshBeforeUpdate: whether NHibernate should refresh the properties of an entity before updating or deleting it;
    • MaximumRecords: the optional maximum number of records to retrieve, if paging is not used (PageSize and PageIndex);
    • PageIndex: the page index to retrieve;
    • PageSize: the page size to retrieve;
    • SessionFactory: a session factory that will be used instead of a default created one;
    • SelectParameters: a collection of parameters to be applied to the Hql string;
    • InsertParameters: a collection of parameters for the insert operation;
    • UpdateParameters: a collection of parameters for the update operation;
    • DeleteParameters: a collection of parameters for the delete operation.


    And, of course, exposes the basic operations: select is the default, but Insert, Update and Delete methods are available.

    NHibernateDataSource will check if the SessionFactory property is set, otherwise, it will build its own Configuration instance and raise the Configure and BuildSessionFactory events. The generated session factory is then stored in the InternalSessionFactory static property for caching.

    Then, the NHibernateDataSourceView, which is the responsible for the actual querying, inheriting from QueryableDataSourceView:

       1: public class NHibernateDataSourceView : QueryableDataSourceView
       2: {
       3:     private static readonly MethodInfo queryMethod = typeof (LinqExtensionMethods).GetMethod("Query", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(IStatelessSession) }, null );
       4:     private static readonly MethodInfo toListMethod = typeof(Enumerable).GetMethod("ToList", BindingFlags.Public | BindingFlags.Static);
       5:  
       6:     public NHibernateDataSourceView(NHibernateDataSource dataSource, String viewName, HttpContext context) : base(dataSource, viewName, context)
       7:     {
       8:         this.DataSource = dataSource;
       9:     }
      10:  
      11:     protected NHibernateDataSource DataSource
      12:     {
      13:         get;
      14:         private set;
      15:     }
      16:  
      17:     protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
      18:     {
      19:         using (var session = this.DataSource.EffectiveSessionFactory.OpenStatelessSession())
      20:         {
      21:             var results = null as IList;
      22:  
      23:             switch (this.DataSource.Mode)
      24:             {
      25:                 case NHibernateDataSourceMode.Hql:
      26:                 {
      27:                     if (String.IsNullOrWhiteSpace(this.DataSource.Hql) == true)
      28:                     {
      29:                         throw (new InvalidOperationException("The Hql property cannot be empty."));
      30:                     }
      31:  
      32:                     var hql = this.DataSource.Hql;
      33:                     var parameters = this.DataSource.GetParameters(this.DataSource.SelectParameters);
      34:                     var args = new EntitiesSelectingEventArgs(hql, parameters, this.DataSource.PageSize, this.DataSource.PageIndex, this.DataSource.MaximumRecords);
      35:  
      36:                     this.DataSource.ProcessEntitiesSelecting(args);
      37:  
      38:                     var query = session.CreateQuery(args.Hql);
      39:  
      40:                     foreach (var param in args.SelectParameters)
      41:                     {
      42:                         if (!(param.Value is IEnumerable) || (param.Value is String) || (param.Value is Byte[]))
      43:                         {
      44:                             query.SetParameter(param.Key, param.Value);
      45:                         }
      46:                         else
      47:                         {
      48:                             query.SetParameterList(param.Key, param.Value as IEnumerable);
      49:                         }
      50:                     }
      51:  
      52:                     if (args.PageSize != 0)
      53:                     {
      54:                         query.SetMaxResults(args.PageSize);
      55:                         query.SetFirstResult(Math.Max((args.PageIndex * args.PageSize) - 1, 0));
      56:                         arguments.MaximumRows = args.PageSize;
      57:                     }
      58:  
      59:                     if (args.MaximumRecords != 0)
      60:                     {
      61:                         query.SetMaxResults(args.MaximumRecords);
      62:                         arguments.MaximumRows = args.MaximumRecords;
      63:                     }
      64:  
      65:                     results = query.List();
      66:  
      67:                     arguments.AddSupportedCapabilities(DataSourceCapabilities.Page);
      68:  
      69:                     if (args.PageSize != 0)
      70:                     {
      71:                         arguments.StartRowIndex = Math.Max((args.PageIndex * args.PageSize) - 1, 0);
      72:                     }
      73:  
      74:                     break;
      75:                 }
      76:  
      77:                 case NHibernateDataSourceMode.Linq:
      78:                 {
      79:                     if (String.IsNullOrWhiteSpace(this.DataSource.EntityName) == true)
      80:                     {
      81:                         throw (new InvalidOperationException("The EntityName property cannot be empty."));
      82:                     }
      83:  
      84:                     var query = queryMethod.MakeGenericMethod(this.EntityType).Invoke(null, new Object[] { session }) as IQueryable;
      85:  
      86:                     var qcea = new QueryCreatedEventArgs(query);
      87:  
      88:                     this.OnQueryCreated(qcea);
      89:  
      90:                     var esaea = new EntitiesSelectingEventArgs(qcea.Query);
      91:  
      92:                     this.DataSource.ProcessEntitiesSelecting(esaea);
      93:  
      94:                     results = toListMethod.MakeGenericMethod(this.EntityType).Invoke(null, new Object[] { esaea.Query }) as IList;
      95:  
      96:                     arguments.AddSupportedCapabilities(DataSourceCapabilities.Page | DataSourceCapabilities.Sort);
      97:  
      98:                     break;
      99:                 }
     100:             }
     101:  
     102:             var entitiesSelectedArgs = new EntitiesSelectedEventArgs(results);
     103:  
     104:             this.DataSource.ProcessEntitiesSelected(entitiesSelectedArgs);
     105:  
     106:             return (entitiesSelectedArgs.Results);
     107:         }
     108:     }
     109:  
     110:     protected override Type EntityType
     111:     {
     112:         get
     113:         {
     114:             return (this.DataSource.GetMetadata(this.DataSource.EntityName).GetMappedClass(EntityMode.Poco));
     115:         }
     116:     }
     117:  
     118:     protected override Object GetSource(QueryContext context)
     119:     {
     120:         throw new NotImplementedException();
     121:     }
     122:  
     123:     protected override void HandleValidationErrors(IDictionary<String, Exception> errors, DataSourceOperation operation)
     124:     {
     125:     }
     126: }

    And the NHibernateDataSourceMode:

       1: public enum NHibernateDataSourceMode
       2: {
       3:     Linq,
       4:     Hql
       5: }

    Finally, all of the event arguments:

       1: [Serializable]
       2: public sealed class BuildSessionFactoryEventArgs : EventArgs
       3: {
       4:     public ISessionFactory SessionFactory
       5:     {
       6:         get;
       7:         set;
       8:     }
       9: }
      10:  
      11: [Serializable]
      12: public sealed class ConfigureEventArgs : EventArgs
      13: {
      14:     public Configuration Configuration
      15:     {
      16:         get;
      17:         set;
      18:     }
      19: }
      20:  
      21: [Serializable]
      22: public sealed class CreateInstanceEventArgs : EventArgs
      23: {
      24:     public CreateInstanceEventArgs(Type type, Object instance)
      25:     {
      26:         this.Type = type;
      27:         this.Instance = instance;
      28:     }
      29:  
      30:     public Type Type
      31:     {
      32:         get;
      33:         private set;
      34:     }
      35:  
      36:     public Object Instance
      37:     {
      38:         get;
      39:         set;
      40:     }
      41: }
      42:  
      43: [Serializable]
      44: public sealed class EntitiesSelectedEventArgs : EventArgs
      45: {
      46:     public EntitiesSelectedEventArgs(IList results)
      47:     {
      48:         this.Results = results;
      49:     }
      50:  
      51:     public IList Results
      52:     {
      53:         get;
      54:         set;
      55:     }
      56: }
      57:  
      58: [Serializable]
      59: public sealed class EntitiesSelectingEventArgs : EventArgs
      60: {
      61:     public EntitiesSelectingEventArgs(IQueryable query)
      62:     {
      63:         this.Query = query;
      64:     }
      65:  
      66:     public EntitiesSelectingEventArgs(String hql, IDictionary<String, Object> selectParameters, Int32 pageSize, Int32 pageIndex, Int32 maximumRecords)
      67:     {
      68:         this.Hql = hql;
      69:         this.SelectParameters = selectParameters;
      70:         this.PageSize = pageSize;
      71:         this.PageIndex = pageIndex;
      72:         this.MaximumRecords = maximumRecords;
      73:     }
      74:  
      75:     public IQueryable Query
      76:     {
      77:         get;
      78:         set;
      79:     }
      80:  
      81:     public String Hql
      82:     {
      83:         get;
      84:         set;
      85:     }
      86:  
      87:     public IDictionary<String, Object> SelectParameters
      88:     {
      89:         get;
      90:         private set;
      91:     }
      92:  
      93:     public Int32 PageSize
      94:     {
      95:         get;
      96:         set;
      97:     }
      98:  
      99:     public Int32 PageIndex
     100:     {
     101:         get;
     102:         set;
     103:     }
     104:  
     105:     public Int32 MaximumRecords
     106:     {
     107:         get;
     108:         set;
     109:     }
     110: }
     111:  
     112: [Serializable]
     113: public sealed class EntityEventArgs : CancelEventArgs
     114: {
     115:     public EntityEventArgs(Object entity)
     116:     {
     117:         this.Entity = entity;
     118:     }
     119:  
     120:     public Object Entity
     121:     {
     122:         get; 
     123:         set;
     124:     }
     125: }
     126:  
     127: [Serializable]
     128: public sealed class OperationCompletedEventArgs : EventArgs
     129: {
     130:     public OperationCompletedEventArgs(DataSourceOperation operation, Object entity)
     131:     {
     132:         this.Entity = entity;
     133:         this.Operation = operation;
     134:     }
     135:  
     136:     public OperationCompletedEventArgs(DataSourceOperation operation, IList results)
     137:     {
     138:         this.Results = results;
     139:         this.Operation = operation;
     140:     }
     141:  
     142:     public DataSourceOperation Operation
     143:     {
     144:         get;
     145:         private set;
     146:     }
     147:  
     148:     public Object Entity
     149:     {
     150:         get;
     151:         protected set;
     152:     }
     153:  
     154:     public IList Results
     155:     {
     156:         get;
     157:         private set;
     158:     }
     159: }

    Now, let’s see concrete examples of its usage. First, using Mode Hql:

       1: <nh:NHibernateDataSource runat="server" ID="nhds" RefreshBeforeUpdate="true" EntityName="Product" Mode="Hql" Hql="from Product p where size(p.OrderDetails) > :size">
       2:     <SelectParameters>
       3:         <asp:Parameter Name="size" DefaultValue="1" Type="Int32" />
       4:     </SelectParameters>
       5:     <InsertParameters>
       6:         <asp:Parameter Name="Name" DefaultValue="Some Name" Type="String" />
       7:         <asp:Parameter Name="Price" DefaultValue="100" Type="Decimal" />
       8:     </InsertParameters>
       9:     <UpdateParameters>
      10:         <asp:QueryStringParameter Name="ProductId" QueryStringField="ProductId" Type="Int32" />
      11:         <asp:QueryStringParameter Name="Price" DefaultValue="50" Type="Decimal" />
      12:     </UpdateParameters>
      13:     <DeleteParameters>
      14:         <asp:QueryStringParameter Name="ProductId" QueryStringField="ProductId" Type="Int32" />
      15:     </DeleteParameters>
      16: </nh:NHibernateDataSource>

    You can see that the Hql property has a parameter, price, which is bound to a parameter in SelectParameters with the same name. Each parameter is an instance of the Parameter class, here I am using a parameter with a static value (Parameter) and another that takes a value from the query string (QueryStringParameter), but others exist. To help with NHibernate insert and update operations, I created a new Parameter class, EntityParameter, that knows how to retrieve a en entity or a proxy to an entity:

       1: public sealed class EntityParameter : Parameter
       2: {
       3:     public EntityParameter()
       4:     {
       5:         this.Lazy = true;
       6:     }
       7:  
       8:     public String EntityName
       9:     {
      10:         get;
      11:         set;
      12:     }
      13:  
      14:     public Object Id
      15:     {
      16:         get;
      17:         set;
      18:     }
      19:  
      20:     public Boolean Lazy
      21:     {
      22:         get;
      23:         set;
      24:     }
      25:  
      26:     protected override Parameter Clone()
      27:     {
      28:         return (new EntityParameter(){ EntityName = this.EntityName, Id = this.Id });
      29:     }
      30:  
      31:     protected override Object Evaluate(HttpContext context, Control control)
      32:     {
      33:         var dataSource = control as NHibernateDataSource;
      34:  
      35:         if (dataSource == null)
      36:         {
      37:             throw (new InvalidOperationException("EntityParameter can only be used with NHibernateDataSource."));
      38:         }
      39:  
      40:         using (var session = dataSource.EffectiveSessionFactory.OpenStatelessSession())
      41:         {
      42:             var metadata = dataSource.GetMetadata(this.EntityName);
      43:  
      44:             if (metadata == null)
      45:             {
      46:                 throw (new InvalidOperationException("Entity could not be found."));
      47:             }
      48:  
      49:             var entityType = metadata.GetMappedClass(EntityMode.Poco);
      50:             var idType = metadata.IdentifierType.ReturnedClass;
      51:             var id = Convert.ChangeType(this.Id, idType);
      52:             var entity = (this.Lazy == true) ? (metadata as IEntityPersister).CreateProxy(id, session.GetSessionImplementation()) : session.Get(entityType.FullName, id);
      53:  
      54:             return (entity);
      55:         }
      56:     }
      57: }

    As for Mode Linq, an example using a QueryExtender is in order:

       1: <asp:TextBox runat="server" ID="name"/>
       2:  
       3: <asp:QueryExtender runat="server" TargetControlID="nhds">
       4:     <asp:SearchExpression DataFields="Name" SearchType="StartsWith">
       5:         <asp:ControlParameter ControlID="name" />
       6:     </asp:SearchExpression>
       7:     <asp:OrderByExpression DataField="Price" Direction="Descending" />
       8: </asp:QueryExtender>
       9:  
      10: <nh:NHibernateDataSource runat="server" ID="nhds" RefreshBeforeUpdate="true" EntityName="Product" Mode="Linq">
      11:     <InsertParameters>
      12:         <asp:Parameter Name="Name" DefaultValue="Some Name" Type="String" />
      13:         <asp:Parameter Name="Price" DefaultValue="100" Type="Decimal" />
      14:     </InsertParameters>
      15:     <UpdateParameters>
      16:         <asp:QueryStringParameter QueryStringField="ProductId" Name="ProductId" Type="Int32" />
      17:         <asp:Parameter Name="Price" DefaultValue="50" Type="Decimal" />
      18:     </UpdateParameters>
      19:     <DeleteParameters>
      20:         <asp:QueryStringParameter QueryStringField="ProductId" Name="ProductId" Type="Int32" />
      21:     </DeleteParameters>
      22: </nh:NHibernateDataSource>

    The LINQ query produced by the NHibernateDataSource is intercepted by the QueryExtender and a where (SearchExpression) and a order by (OrderByExpression) clauses are added to it. Other expressions can be used, inheriting from DataSourceExpression, and some take parameters of type Parameter. Do notice that filtering and sorting is performed server-side, not client-side.

    Of course, this can certainly be improved, let me hear your thoughts and questions.

    And that’s it. Enjoy!

    Read more...

  • Custom XSLT Functions in SharePoint

    Wouldn’t it be good if SharePoint would allow the registration of custom functions that could be used in XSLT stylesheets? Well, as it happens, it does!

    Some of the classes that allow XSLT processing – DataFormWebPart, ContentByQueryWebPart - have a virtual method called ModifyXsltArgumentList that can be used for registering custom functions. Sadly, XsltListViewWebPart, XlstListFormWebPart, among others, are sealed, and thus do not allow this.

    Let’s write a class that inherits from DataFormWebPart (you could the same for ContentByQueryWebPart) and register our own functions:

       1: public class CustomDataFormWebPart : DataFormWebPart
       2: {
       3:     protected override void ModifyXsltArgumentList(ArgumentClassWrapper argList)
       4:     {
       5:         argList.AddExtensionObject("urn:MyFunctions", new MyFunctions());
       6:         base.ModifyXsltArgumentList(argList);
       7:     }
       8: }

    Let’s forget about the MyFunctions class and pretend that it has a few public instance methods that we want to expose to SharePoint. Mind you, because this class will be instantiated in a SharePoint context, it will have access to SPContext.Current, and from here, to the whole of SharePoint!

    In a XSLT script, we need something like this in order to access our extension functions:

       1: <xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:dsp="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal" xmlns:my="urn:MyFunctions">
       2: ...
       3: </xsl:stylesheet>

    Did you notice xmlns:my=”urn:MyFunctions”? This should be identical to the namespace that you used inside ModifyXsltArgumentList.

    And to call one of the functions:

       1: <xsl:value-of select="my:Function()"/>

    Again, you need to use the same prefix, my in this case. You can add parameters, as long as they are of base types (strings, number, date/time, booleans, and so on), and the same goes for the result type.

    I hear you say: “OK, but now I have to replace all declarations of DataFormWebPart for this new CustomDataFormWebPart!”. Not really, dear reader, ASP.NET offers a great solution for this: tag mapping. In a nutshell, we can ask ASP.NET to replace all instances of a control for another class that inherits from the control’s class.

    Now, we could this in several ways, but the best one is to have a feature targeting a WebApplication with a feature receiver that performs a Web.config modification:

       1: public override void FeatureActivated(SPFeatureReceiverProperties properties)
       2: {
       3:     var tagMappingDataFormWebPartModification = new SPWebConfigModification();
       4:     tagMappingDataFormWebPartModification.Path = "configuration/system.web/pages/tagMapping";
       5:     tagMappingDataFormWebPartModification.Name = "tagMappingDataFormWebPartModification";
       6:     tagMappingDataFormWebPartModification.Sequence = 0;
       7:     tagMappingDataFormWebPartModification.Owner = "WebConfigModifications";
       8:     tagMappingDataFormWebPartModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
       9:     tagMappingDataFormWebPartModification.Value = String.Format("<add tagType=\"{0}\" mappedTagType=\"{1}\"/>", typeof(DataFormWebPart).FullName, typeof(CustomDataFormWebPart).AssemblyQualifiedName);
      10:  
      11:     var webApp = properties.Feature.Parent as SPWebApplication;
      12:     webApp.WebConfigModifications.Add(tagMappingDataFormWebPartModification);
      13:     webApp.Update();
      14:     webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
      15: }

    When this feature is activated, it adds a line in the Web.config file, tagMapping section, that tells ASP.NET to replace DataFormWebPart for CustomDataFormWebPart in all markup declarations. Of course, it won’t affect code: new DataFormWebPart() will still return a DataFormWebPart!

    All is not done, yet. We still need to mark our CustomDataFormWebPart control as safe for declaration in web pages. We do that by adding an entry to the SafeControls section of the Web.config. For a change, I’m going to do this in the SharePoint Solution Manifest, Package.xml:

       1: <Solution xmlns="http://schemas.microsoft.com/sharepoint/" SolutionId="e860c419-b399-497d-a313-545f994753c8" SharePointProductVersion="15.0">
       2:   <Assemblies>
       3:     <Assembly Location="$SharePoint.Project.AssemblyFileName$" DeploymentTarget="GlobalAssemblyCache">
       4:       <SafeControls>
       5:         <SafeControl Assembly="$SharePoint.Project.AssemblyFullName$" Namespace="MyNamespace" TypeName="*" Safe="True" />
       6:       </SafeControls>
       7:     </Assembly>
       8:   </Assemblies>
       9:   <FeatureManifests>
      10:     <FeatureManifest Location="MyFeature\Feature.xml" />
      11:   </FeatureManifests>
      12: </Solution>

    Notice the $SharePoint.Project.AssemblyFileName$ and $SharePoint.Project.AssemblyFullName$, these are two examples of tokens that will be replaced by Visual Studio when it deploys the solution.

    And that’s it! Your custom XSLT extension functions are ready to use!

    Read more...

  • A Tour of SharePoint Data Source Controls

    Introduction

    Besides the classic ASP.NET data source controls, SharePoint brings along its own. These allow us to retrieve data not just from SharePoint, but also from external sources, such as web services or SQL databases. Using SharePoint Designer you end up using these data sources without even knowing, and, specially, ignoring what other options you have. Let’s look at these controls one by one and see how the can be used. I already talked about the SPDataSource and AggregateDataSource, so I won’t cover these here. I also won’t be covering them in depth, but instead will provide an example of how to use them.

    SPHierarchyDataSourceControl

    The SPHierarchyDataSourceControl allows us to retrieve data (documents, list items) hierarchically from either a site or a list. It can only be bound to a control that can display hierarchical data, such as the SPTreeView control. It cannot be easily customized, except by using CSS and JavaScript.

    A simple example is:

       1: <SharePoint:SPHierarchyDataSourceControl runat="server" ID="hierarchy" RootContextObject="Web" ShowDocLibChildren="true" ShowListChildren="true" ShowFolderChildren="true" ShowWebChildren="true"/>
       2: <SharePoint:SPTreeView runat="server" DataSourceID="hierarchy"/>

    BdcDataSource

    A BdcDataSource let’s you retrieve data from a BCS data source (external content type). This may happen when you don’t have an external list created or you want to call a specific finder method. You need to specify the namespace, LOB instance, entity, and finder method. If the finder needs parameters, you will need to supply them. It can be customized by hosting it in a DataFormWebPart and by applying XSLT.

    Here’s an example:

       1: <WebPartPages:DataFormWebPart runat="server" Title="BCS" DisplayName="BCS" ID="bcs">
       2:     <DataSources>
       3:         <SharePoint:BdcDataSource runat="server" Mode="List" EntityName="Parent" LobSystemInstanceName="BCS" EntityNamespace="http://sp2013" FinderName="Read List"/>
       4:     </DataSources>
       5:     <Xsl>
       6:         <xsl:stylesheet version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
       7:             <xsl:output method="html" indent="no"/>
       8:  
       9:                 <xsl:template match="/">
      10:                     <xsl:variable name="Rows" select="/dsQueryResponse/NewDataSet/Row"/>
      11:                     Count: <xsl:value-of select="count($Rows)"/>
      12:                 </xsl:template>
      13:             
      14:         </xsl:stylesheet>
      15:     </Xsl>
      16: </WebPartPages:DataFormWebPart>

    SoapDataSource

    SoapDataSource is the control that retrieves data from a SOAP web service. You specify the URL of the web service, the action to call and the SOAP envelope, together with any required parameters. It should be hosted in a DataFormWebPart and can thus use XSLT for the formatting of its contents.

    An example of calling the Lists.asmx web service:

       1: <WebPartPages:DataFormWebPart runat="server" DisplayName="SOAP" Title="SOAP" ID="soap">
       2:     <DataSources>
       3:         <SharePoint:SoapDataSource runat="server" WsdlPath="http://sp2013/_vti_bin/lists.asmx?WSDL" SelectUrl="http://sp2013/_vti_bin/lists.asmx" SelectAction="http://schemas.microsoft.com/sharepoint/soap/GetListCollection" SelectPort="ListsSoap" SelectServiceName="Lists">
       4:             <SelectCommand>
       5:                 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
       6:                     <soap:Body>
       7:                         <GetListCollection xmlns="http://schemas.microsoft.com/sharepoint/soap/"/>
       8:                     </soap:Body>
       9:                 </soap:Envelope>
      10:             </SelectCommand>
      11:         </SharePoint:SoapDataSource>
      12:     </DataSources>
      13:     <XSL>
      14:         <xsl:stylesheet xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ddw1="http://schemas.microsoft.com/sharepoint/soap/" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
      15:             <xsl:output method="html" indent="no"/>
      16:     
      17:             <xsl:template match="/">
      18:                 <xsl:variable name="Rows" select="/soap:Envelope/soap:Body/ddw1:GetListCollectionResponse/ddw1:GetListCollectionResult/ddw1:Lists/ddw1:List"/>
      19:                 Count: <xsl:value-of select="count($Rows)"/>
      20:             </xsl:template>
      21:         </xsl:stylesheet>
      22:     </XSL>
      23: </WebPartPages:DataFormWebPart>

    XsltListViewWebPart (External List)

    External lists can be displayed using the XsltListViewWebPart. Nothing really new here.

    A simple example (of course, do replace the list and view GUIDs):

       1: <WebPartPages:XsltListViewWebPart runat="server" Title="External List" DisplayName="External List" ListName="{C9DD692D-27C6-4B99-A197-F8892F293C04}" ID="external">
       2:     <XmlDefinition>
       3:         <View Name="{DDD613DD-BA38-4DBF-9A89-5502B0152EE1}" Type="HTML" DisplayName="Read List" Url="/Lists/ExternalList/Read List.aspx" BaseViewID="1">
       4:             <Method Name="Read List"/>
       5:         </View>
       6:     </XmlDefinition>
       7:     <Xsl>
       8:         <xsl:stylesheet version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
       9:             <xsl:output method="html" indent="no"/>
      10:  
      11:                 <xsl:template match="/dsQueryResponse">
      12:                     <xsl:variable name="Rows" select="Rows/Row"/>
      13:                     Count: <xsl:value-of select="count($Rows)"/>    
      14:                 </xsl:template>
      15:         </xsl:stylesheet>
      16:     </Xsl>
      17: </WebPartPages:XsltListViewWebPart>

    XmlUrlDataSource

    The XmlUrlDataSource is used for invoking REST web services. Similar to  SoapDataSource, you need to pass it the URL, but also the HTTP method and any parameters. I is also usually hosted in a DataFormWebPart.

    Here’s an example of calling the weather service I talked about in another post:

       1: <WebPartPages:DataFormWebPart runat="server" Title="REST" DisplayName="REST" ID="rest">
       2:     <DataSources>
       3:         <SharePoint:XmlUrlDataSource runat="server" AuthType="None" HttpMethod="GET" SelectCommand="http://api.openweathermap.org/data/2.5/weather">
       4:             <SelectParameters>
       5:                 <WebPartPages:DataFormParameter Name="id" ParameterKey="id" PropertyName="ParameterValues" DefaultValue="2740637"/>
       6:                 <WebPartPages:DataFormParameter Name="mode" ParameterKey="mode" PropertyName="ParameterValues" DefaultValue="xml"/>
       7:                 <WebPartPages:DataFormParameter Name="units" ParameterKey="units" PropertyName="ParameterValues" DefaultValue="metric"/>
       8:             </SelectParameters>
       9:         </SharePoint:XmlUrlDataSource>
      10:     </DataSources>
      11:     <Xsl>
      12:         <xsl:stylesheet version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
      13:             <xsl:output method="html" indent="no"/>
      14:  
      15:                 <xsl:template match="/">
      16:                     <xsl:variable name="Rows" select="/current"/>
      17:                     Count: <xsl:value-of select="count($Rows)"/>
      18:                 </xsl:template>
      19:             
      20:         </xsl:stylesheet>
      21:     </Xsl>
      22: </WebPartPages:DataFormWebPart>

    SPSqlDataSource

    The SPSqlDataSource is what SharePoint uses to access a SQL database. If you use SharePoint Designer to add one such data source, you might be surprised to see that it places a SqlDataSource instead. The thing is, SharePoint uses ASP.NET tag mapping to replace any SqlDataSource control for a SPSqlDataSource. Again, we host it inside a DataFormWebPart for easily formatting its contents and you can pass parameters to the SQL.

    An example:

       1: <WebPartPages:DataFormWebPart runat="server" Title="SQL" DisplayName="SQL" ID="sql">
       2:     <DataSources>
       3:         <SharePoint:SPSqlDataSource runat="server" ProviderName="System.Data.SqlClient" ConnectionString="Data Source=servername;User ID=username;Password=password;Initial Catalog=database;" SelectCommand="SELECT * FROM [SomeTable]"/>
       4:     </DataSources>
       5:     <Xsl>
       6:         <xsl:stylesheet xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
       7:             <xsl:output method="html" indent="no"/>
       8:  
       9:                 <xsl:template match="/">
      10:                     <xsl:variable name="Rows" select="/dsQueryResponse/NewDataSet/Row"/>
      11:                     Count: <xsl:value-of select="count($Rows)"/>
      12:                 </xsl:template>
      13:         </xsl:stylesheet>
      14:     </Xsl>
      15: </WebPartPages:DataFormWebPart>

    SPCalendarDataSource

    SPCalendarDataSource cannot be used exclusively on the markup, it needs a List property that can only be set through code. It is usually used to populate a SPCalendarView control.

    An example of the markup:

       1: <SharePoint:SPCalendarDataSource runat="server" ID="calendar" ViewType="Week"/>
       2: <SharePoint:SPCalendarView runat="server" DataSourceID="calendar"/>

    Conclusion

    While SPDataSource is generally the most generic and useful control, it cannot do everything, namely, access external sources. For that, we have other options; the AggregateDataSource can be used to bring together data from all of these sources, except SPHierarchyDataSourceControl and SPCalendarDataSource, but you can easily replace these by SPDataSource. I did not mention SPWorkflowDataSource because it isn't a general-purpose data source control, it is merely used to set or get workflow association parameters.

    Let me know if you have any questions!

    Read more...

  • Aggregating Lists in SharePoint

    Another post that could be subtitled “How to do Things in SharePoint By Hand”. This time, combining data sources – lists, in this case, but the same can be applied to other source types, such as SOAP (SoapDataSource) or REST (XmlUrlDataSource) web services, hierarchies of sites (SPHierarchyDataSourceControl), calendar (SPCalendarDataSource), XML files (SPXmlDataSource), BCS external lists (BdcDataSource) or even SQL queries (SPSqlDataSource).

    We will query two lists, using an SPDataSource for each, and then we’ll combine the results with an AggregateDataSource and finally we’ll display the results using DataFormWebPart and XSLT.

    Enough talk, here is the code:

       1: <WebPartPages:DataFormWebPart runat="server" ID="data">
       2:     <DataSources>
       3:         <SharePoint:AggregateDataSource runat="server" ID="aggregates" SeparateRoot="True">
       4:             <Sources>
       5:                 <SharePoint:SPDataSource runat="server" ID="tasks" DataSourceMode="List" UseInternalName="True" UseServerDataFormat="True" SelectCommand="&lt;View><Query></Query></View>">
       6:                     <SelectParameters>
       7:                         <WebPartPages:DataFormParameter Name="ListName" ParameterKey="ListName" PropertyName="ParameterValues" DefaultValue="Tasks"/>
       8:                     </SelectParameters>
       9:                     </SharePoint:SPDataSource>
      10:                 <SharePoint:SPDataSource runat="server" ID="pages" DataSourceMode="List" UseInternalName="True" UseServerDataFormat="True" SelectCommand="&lt;View><Query></Query></View>">
      11:                     <SelectParameters>
      12:                         <WebPartPages:DataFormParameter Name="ListName" ParameterKey="ListName" PropertyName="ParameterValues" DefaultValue="Pages"/>
      13:                     </SelectParameters>
      14:                 </SharePoint:SPDataSource>
      15:             </Sources>
      16:             <Aggregate>
      17:                 <concat name="data source">
      18:                     <datasource name="Tasks" id="0" type="SPList"/>
      19:                     <datasource name="Pages" id="1" type="SPList"/>
      20:                 </concat>
      21:             </Aggregate>
      22:         </SharePoint:AggregateDataSource>               
      23:     </DataSources>              
      24:     <Xsl>            
      25:         <xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal" xmlns:agg="http://schemas.microsoft.com/sharepoint/aggregatesource">
      26:               <xsl:output method="html" indent="no"/>                  
      27:               <xsl:template match="/">                                
      28:                       <xsl:for-each select="/dsQueryResponse/Tasks/Rows/Row">
      29:                           <xsl:variable name="AuthorID" select="@Author.id"/>
      30:                           <p>Task Title: <xsl:value-of select="@Title"/></p>
      31:                           <p>Pages of same author: <xsl:value-of select="count(/dsQueryResponse/Pages/Rows/Row[@Author.id = $AuthorID])"/></p>
      32:                       </xsl:for-each>
      33:                   </xsl:template>                    
      34:         </xsl:stylesheet>
      35:     </Xsl>
      36: </WebPartPages:DataFormWebPart>

    Noteworthy:

    • If you specify the SeparateRoot parameter as true, in the XSL section, you will get two data sources, one for each data source specified as a concat/datasource entry, you will have to access them separately, using syntax /dsQueryResponse/Tasks/Rows/Row, of course, replacing Tasks for the right name, as specified in the concat/datasource’s name attribute;
    • If you don’t specify a value for SeparateRoot, the default is false, which means that the values for both data sources will come together in /dsQueryResponse/Rows/Row; you can sort out what records belong to one list or the other using the syntax /dsQueryResponse/Rows[@agg:source='Pages']/Row; this is made possible by the xmlns:agg="http://schemas.microsoft.com/sharepoint/aggregatesource" entry in the xsl:stylesheet declaration, which I’m sure you hadn’t noticed Smile;
    • I am using DataFormParameters to specify the source for the SPDataSources as a list name, instead of a list id. This makes it more portable;
    • Although I am not filtering the lists, you probably should, there are several ways to do this, which usually include using the SelectCommand together with SelectParameters;
    • In the XSLT, I am iterating through the first list, and for each item, I get one field value (Author = Creation User) and use it to filter the second list. This is the same that SharePoint Designer does when you insert a linked list data source.

    And that’s it. Enjoy!

    Read more...

  • Displaying Last Modified Contents in SharePoint

    SharePoint, as usual, offers lots of ways to do things. This time, I’m going to show how one can get a list of the last modified contents, in any SharePoint version and edition.

    We’ll use our good friends DataFormWebPart and SPDataSource, together with some XSLT and some nice tricks. We will be able to specify the number of items to return through the query string, otherwise it will default to 5. I’m going to keep it simple, but you can certainly improve it.

    We add a DataFormWebPart with an inline SPDataSource that uses DataSourceMode of  CrossList. This allows querying multiple lists at once. The actual query is specified in the SelectCommand property. In this case, I am specifying it as:

       1: <View><Webs Scope='Recursive'></Webs><Lists ServerTemplate='119'></Lists><RowLimit>{TopN}</RowLimit><Query><Where><And><Contains><FieldRef Name='FileLeafRef'/><Value Type='Text'>.aspx</Value></Contains><Eq><FieldRef Name='FSObjType'/><Value Type='Number'>0</Value></Eq></And></Where><OrderBy><ListProperty Name='Title' Ascending='false'/></OrderBy></Query><ViewFields><ListProperty Name='Title'/><ListProperty Name='ID'/><FieldRef Name='FSObjType'/><FieldRef Name='ID'/><FieldRef Name='Title'/><FieldRef Name='FileRef'/><FieldRef Name='FileDirRef'/><FieldRef Name='FileLeafRef'/><FieldRef Name='Editor'/><FieldRef Name='Modified'/></ViewFields></ViewFields></View>

    What it means is:

    • Search recursively all subsites (Scope=’Recursive’);
    • Only search lists and document libraries of type Wiki Page Library (ServerTemplate=’119);
    • Limit the returned results to the value passed in parameter TopN (RowLimit);
    • Only return files (FSObjType = 0) containing .aspx in its name;
    • Return fields List Title (ListProperty Name=’Title’), List ID (ListProperty Name=’ID’), Object Type (FSObjType), ID, Title, File Location (FileRef), File Directory (FileDirRef), File Name (FileLeafRef), Last Modification User (Editor) and Last Modification Timestamp (Modified);
    • Order by the List Title.

    The markup for the DataFormWebPart is as follows:

       1: <WebPartPages:DataFormWebPart runat="server" ChromeType="None" ID="recentContents">
       2:     <ParameterBindings>
       3:         <ParameterBinding Name="TopN" Location="QueryString(TopN)" DefaultValue="5"/>
       4:     </ParameterBindings>
       5:     <Xsl>
       6:         <xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
       7:           <xsl:output method="html" indent="no"/>
       8:               <xsl:param name="TopN"/>
       9:                           
      10:               <xsl:template match="/">
      11:                 <xsl:variable name="rows" select="/dsQueryResponse/Rows/Row"/>
      12:                 <table>
      13:                     <tr>
      14:                         <th>Name</th>
      15:                         <th>Last Modified At</th>
      16:                         <th>Last Modified By</th>
      17:                     </tr>
      18:                     <xsl:for-each select="$rows">
      19:                           <xsl:variable name="IsNewList" select="ddwrt:NameChanged(@ListProperty.Title, 0)"/>
      20:                           <xsl:if test="$IsNewList != ''">
      21:                               <tr>
      22:                                   <td colspan="3">
      23:                                       <a href="/{substring-after(@FileDirRef, ';#')}" title="{@ListProperty.Title}" target="_blank">
      24:                                           <xsl:attribute name="title">
      25:                                               <xsl:value-of select="@ListProperty.Title"/>
      26:                                           </xsl:attribute>
      27:                                           <xsl:value-of select="@ListProperty.Title"/>
      28:                                       </a>
      29:                                   </td>
      30:                               </tr>
      31:                             <xsl:call-template name="body">
      32:                                   <xsl:with-param name="rows" select="$rows[@ListProperty.Title = current()/@ListProperty.Title]"/>
      33:                             </xsl:call-template>
      34:                           </xsl:if>
      35:                     </xsl:for-each>
      36:                 </table>
      37:               </xsl:template>
      38:                                               
      39:               <xsl:template name="body">
      40:                 <xsl:param name="rows"/>
      41:                 <xsl:for-each select="$rows">
      42:                       <xsl:sort select="ddwrt:FormatDateTime(string(@Modified), 1033, 'yyyyMMdd HHmmss')" order="descending"/>
      43:                     <xsl:call-template name="item"/>
      44:                 </xsl:for-each>
      45:               </xsl:template>
      46:         
      47:               <xsl:template name="item">
      48:                 <tr>
      49:                     <td>
      50:                         <a href="/{substring-after(@FileRef, '#')}" title="{@Title}" target="_blank">
      51:                         <xsl:value-of select="substring-after(substring-before(@FileLeafRef, '.aspx'), '#')"/>
      52:                         </a>
      53:                     </td>
      54:                         <xsl:value-of select="ddwrt:FormatDate(string(@Modified), 1033, 5)"/>
      55:                     </td>
      56:                     <td>
      57:                         <a target="_blank" href="/_layouts/userdisp.aspx?ID={@Editor}">
      58:                                 <xsl:value-of select="substring-after(@Editor, ';#')"/>
      59:                         </a>
      60:                     </td>
      61:                 </tr>
      62:           </xsl:template>
      63:         </xsl:stylesheet>
      64:     </Xsl>
      65:     <DataSources>        
      66:         <SharePoint:SPDataSource runat="server" DataSourceMode="CrossList" SelectCommand="&lt;View><Webs Scope='Recursive'></Webs><Lists ServerTemplate='119'></Lists><RowLimit>{TopN}</RowLimit><Query><Where><And><Contains><FieldRef Name='FileLeafRef'/><Value Type='Text'>.aspx</Value></Contains><Eq><FieldRef Name='FSObjType'/><Value Type='Number'>0</Value></Eq></And></Where><OrderBy><ListProperty Name='Title'/></OrderBy></Query><ViewFields><ListProperty Name='Title'/><ListProperty Name='ID'/><FieldRef Name='FSObjType'/><FieldRef Name='ID'/><FieldRef Name='Title'/><FieldRef Name='FileRef'/><FieldRef Name='FileDirRef'/><FieldRef Name='FileLeafRef'/><FieldRef Name='Editor'/><FieldRef Name='Modified'/></ViewFields></ViewFields></View>" UseInternalName="True" UseServerDataFormat="True">
      67:             <SelectParameters>
      68:                 <WebPartPages:DataFormParameter Name="TopN" ParameterKey="TopN" PropertyName="ParameterValues" runat="server"/>
      69:             </SelectParameters>
      70:         </SharePoint:SPDataSource>
      71:     </DataSources>
      72: </WebPartPages:DataFormWebPart>

    The ParameterBindings section includes an entry that gets a TopN parameter from the query string, and if one is not there, sets the default to 5. This parameter is passed to the SPDataSource through a DataFormParameter.

    While we are iterating through the results, we call the ddwrt:NameChanged extension function upon the value of ListProperty.Title to check if we have changed to a new list, in which case, we add a new row with just its name. Inside each list, items are obtained where they match the list and sorted by the ddwrt:FormatDateTime function according to its last modification timestamp.

    I think that’s it. Hope you find it useful.

    Read more...

  • Displaying Current Weather Status in SharePoint

    OpenWeatherMap.org offers a nice service for obtaining current and forecast weather conditions. Go visit it, and search for the “weather in your city”. In the results page, by hovering the link, you will get the id for the city you are interested in; for example, for “Coimbra, Portugal”, it is 2740637 (http://openweathermap.org/city/2740637).

    In a SharePoint page, add a XML Viewer Web Part and point it to the address of the OpenWeatherMap.org web service: http://api.openweathermap.org/data/2.5/weather?id=2740637&mode=xml&units=metric. Of course, do replace the id and also the unit, if you like. Documentation for the web services is available at http://openweathermap.org/api. Next, add the following XSLT transformation:

       1: <?xml version="1.0" encoding="UTF-8"?>
       2: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
       3:     <xsl:template match="weather">
       4:         <img>
       5:             <xsl:attribute name="src">http://openweathermap.org/img/w/<xsl:value-of select="@icon"/>.png</xsl:attribute>
       6:             <xsl:attribute name="title"><xsl:value-of select="@value"/></xsl:attribute>
       7:         </img>
       8:     </xsl:template>
       9:     <xsl:template match="temperature">
      10:         <xsl:value-of select="format-number(@value, '#,0')"/>&deg;C
      11:     </xsl:template>
      12: </xsl:stylesheet>

    This will add an image for the current status with description and the current temperature. Yes, I could have used a single xsl:template, but I was lazy!

    After you save the page, you will get a nice status like this:

    image

    I will probably write a post on displaying the forecast. In the meantime, enjoy! Winking smile

    Read more...

  • TextBox With Suggestions in SharePoint

    This control will suggest values for a text box from the values in a SharePoint list. It makes use of the HTML5 datalist element in a slightly customized TextBox control.

       1: public class SPTextBox : TextBox
       2: {
       3:     public SPTextBox()
       4:     {
       5:         this.FieldName = "Title";
       6:     }
       7:  
       8:     [Description("List name")]
       9:     [DefaultValue("")]
      10:     public String ListName
      11:     {
      12:         get;
      13:         set;
      14:     }
      15:  
      16:     [Description("Field name")]
      17:     [DefaultValue("Title")]
      18:     public String FieldName
      19:     {
      20:         get;
      21:         set;
      22:     }
      23:  
      24:     protected override void Render(HtmlTextWriter writer)
      25:     {
      26:         var addList = (this.Visible == true) && (String.IsNullOrWhiteSpace(this.ListName) == false) && (String.IsNullOrWhiteSpace(this.FieldName) == false);
      27:         var dataId = String.Concat(this.ID, "_data");
      28:  
      29:         if (addList == true)
      30:         {
      31:             this.Attributes["list"] = dataId;
      32:         }
      33:  
      34:         base.Render(writer);
      35:  
      36:         if (addList == true)
      37:         {
      38:             var list = SPContext.Current.Web.Lists[this.ListName];
      39:             var values = list.Items.OfType<SPListItem>().Select(x => x[this.FieldName]).Distinct().OrderBy(x => x.ToString());
      40:  
      41:             writer.WriteLine(String.Format("<datalist id=\"{0}\">", dataId));
      42:  
      43:             foreach (var value in values)
      44:             {
      45:                 writer.WriteLine(String.Format("<option value=\"{0}\"/>", value));
      46:             }
      47:  
      48:             writer.WriteLine("</datalist>");
      49:         }
      50:     }
      51: }

    Before you ask, yes, I know about GetDistinctFieldValues, but it is marked as deprecated, which means it may go away in a future version of SharePoint and besides it can’t get distinct values for all fields – Title, for instance.

    You declare it as a regular TextBox, plus a ListName and a FieldName properties:

       1: <my:SPTextBox runat="server" ID="something" ListName="Tasks" FieldName="Title"/>

    And when it runs, it will present something like this:

    image

    Read more...

  • Conditional Content in SharePoint Markup

    In SharePoint, there are several web parts that allow us to have different contents depending on some conditions:

    • LoginView (ASP.NET): allows the definition of templates per authenticated or anonymous user;
    • SPSecurityTrimmedControl: displays contents based on the security permissions of the current user;
    • EditModePanel: for displaying contents in a web part page depending on its edit mode;
    • AuthoringContainer: displays content depending on whether the current user has write or read rights on the current page or if it has an associated list item;
    • DataViewWebPart: allows the passing of parameters and the usage of XSL for rendering logic.

    I imagine you are now rolling your eyes: DataViewWebPart? how come? Well, because it doesn’t need to point to a specific list or view (unlike XsltListViewWebPart), it is very useful for markup-based customizations that will only depend on parameters.

    Let’s see an example:

       1: <WebPartPages:DataFormWebPart runat="server" Title="Conditional Content">
       2:     <ParameterBindings>
       3:         <ParameterBinding Name="MyParameter" Location="QueryString(MyParameter)"/>
       4:     </ParameterBindings>
       5:     <XSL>
       6:         <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl asp" xmlns:asp="System.Web.UI.WebControls">                                                  
       7:             <xsl:param name="MyParameter"/>                                                                                                                                        
       8:             <xsl:template match="/">
       9:                 <asp:Label runat="server" Text="Some content, just to show ASP.NET controls inside a SharePoint DataFormWebPart"/>
      10:                 <xsl:choose>
      11:                     <xsl:when test="$MyParameter=''">
      12:                         No parameter...
      13:                     </xsl:when>
      14:                     <xsl:otherwise>
      15:                         Allright! <xsl:value-of select="$MyParameter"/>
      16:                     </xsl:otherwise>
      17:                 </xsl:choose>                                                                                                
      18:             </xsl:template>
      19:         </xsl:stylesheet>
      20:     </XSL>                                        
      21: </WebPartPages:DataFormWebPart>

    You can use this technique for:

    • Including scripts and stylesheets;
    • Including server-side controls.

    It’s just a matter of rolling out some XSL to the rescue!

    You may be already familiar with the available parameters, but you can find the full list here: http://msdn.microsoft.com/en-us/library/office/ff630170(v=office.15).aspx.

    Another SharePoint Designer-only solution that may come in handy! ;-)

    Read more...