giovedì 30 settembre 2010

Come creare una form Infopath che decida a runtime la Save Location

Titolo un po' criptico ma richiama una problematica che ho dovuto affrontare in questi giorni. Dovevo creare una form InfoPath da usare via browser in SharePoint 2010, che salvasse il suo contento in una raccolta documentale in formato XML; niente di difficile direte voi, basta creare una Data Connection verso la lista SharePoint in cui si vuole salvare il risultato. Esatto, ma la mia document library stava in un sito che andava salvato in un template. Come nella maggior parte delle volte, niente programmazione, niente site definition; tutto creato da browser e con InfoPath 2010 Designer. Dopo una giornata di tentativi (premetto che le mie conoscenze di InfoPath tendevano a meno infinito) leggendo nell'event viewer del server ho scoperto come viene invocata la form InfoPath e da questo, aggiungendo alcuni consigli di Alberto Ballabio (trovati sul suo sito SGART)  e di Massimo Luinetti e un pizzico di aiuto reperito da questo articolo ho scritto del codice per risolvere il mio problema.
Innanzi tutto cosa ho trovato nell'event viewer. Quando creo la form la url invocata è del tipo:
http://<ServerName>/_layouts/FormServer.aspx?XsnLocation=http://srvsp01/FormServerTemplates/formName.xsn&SaveLocation=http://<ServerName>/nav/temp/Changes&Source=http://<ServerName>/nav/temp/Changes/Forms/AllItems.aspx&DefaultItemOpen=1
Quello che ci interessa è il parametro SaveLocation.
Quando riapro il form invece la url invocata è:
http://<ServerName>/_layouts/FormServer.aspx?XmlLocation=/nav/temp/Changes/formName.xml&Source=http://<ServerName>/nav/temp/Changes/Forms/AllItems.aspx&DefaultItemOpen=1
Quindi le due url non sono uguali. Detto questo la soluzione prevede di cambiare a runtime la url della Data Connection di tipo Submit. Anche in questo caso c'è l'inghippo. Infatti ho scoperto a mie spese, che l'unico evento in grado di modificare con successo la url della Data Connection è l'evento di Loading.
Ecco il codice finito e funzionante.
using Microsoft.Office.InfoPath;
using System;
using System.Xml;
using System.Xml.XPath;

namespace caricafiles
{
    public partial class FormCode
    {
        private object _strUri
        {
            get
            {
                return FormState["_strUri"];
            }
            set
            {
                FormState["_strUri"] = value;
            }
        }


        // NOTE: The following procedure is required by Microsoft Office InfoPath.
        // It can be modified using Microsoft Office InfoPath.
        public void InternalStartup()
        {
            EventManager.FormEvents.Loading += new LoadingEventHandler(FormEvents_Loading);
        }

        public void FormEvents_Loading(object sender, LoadingEventArgs e)
        {
            Init(e);       
        }

        // Modificare la url della data connection è possibile solo nel metodo FormEvents_Loading
        private void Init(LoadingEventArgs e)
        {
            //Get the Uri (or SaveLocation in a browser form) of where 
            //the form was opened 
            //See if the form was opened in the browser
            Boolean OpenedInBrowser = Application.Environment.IsBrowser;
            //Get a reference to the submit data connection
            FileSubmitConnection fc = (FileSubmitConnection)this.DataConnections["FOB Save"];

            //If so, we will get the "SaveLocation" from the InputParameters
            if (OpenedInBrowser)
            {
                if (e.InputParameters.ContainsKey("SaveLocation"))
                {
                    _strUri = e.InputParameters["SaveLocation"].ToString();
                }
                else if (e.InputParameters.ContainsKey("Source"))
                {
                    string source = e.InputParameters["Source"].ToString();
                    _strUri = source.Substring(0, source.IndexOf("Forms") - 1);
                }
                else
                {
                    _strUri = _strUri;
                }
            }
            else
            {
                //If it was opened in the client, we will get the Uri
                _strUri = this.Template.Uri.ToString();
            }

            //Modify the URL we want to submit
            fc.FolderUrl = (string)_strUri;
        }
    }
}
Ultima nota: tenete in considerazione la lingua di InfoPath client quando andate a cercare aiuto un internet perchè i nodi della form InfoPath vengono localizzati e quindi il codice XPath di un nodo di una form in inglese risulta diverso da quello di una form in italiano.

martedì 21 settembre 2010

Vulnerabilità di SharePoint 2010

Il team di SharePoint sul proprio blog ha annunciato una vulnerabilità del framework .NET che impatta anche su SharePoint e consiglia di adottare subito il workaraound descritto nell'articolo stesso.
In sostanza bisogna seguire i seguenti passi:
  1. Posizionarsi nella cartella %CommonProgramFiles%\Microsoft Shared\Web Server Extensions\14\template\layouts. 
  2. Creare un nuovo file chiamato error2.aspx nella cartella contenente il seguente codice:


    <%@ Page Language="C#" AutoEventWireup="true" %>
    <%@ Import Namespace="System.Security.Cryptography" %>
    <%@ Import Namespace="System.Threading" %>
    
    <script runat="server">
       void Page_Load() {
          byte[] delay = new byte[1];
          RandomNumberGenerator prng = new RNGCryptoServiceProvider();
    
          prng.GetBytes(delay);
          Thread.Sleep((int)delay[0]);
            
          IDisposable disposable = prng as IDisposable;
          if (disposable != null) { disposable.Dispose(); }
        }
    </script>
    
    
    <html>
    <head runat="server">
        <title>Error</title>
    </head>
    <body>
        <div>
            An error occurred while processing your request.
        </div>
    </body>
    </html> 
    
    
  3. Posizionarsi nella cartella %SystemDrive%\inetpub\wwwroot\wss\virtualdirectories
  4. Per ogni sottocartella fare i seguenti passi:



    1. Edit web.config
    2. Trovare il nodo "customErrors" e cambiarlo come segue


      <customErrors mode="On" redirectMode="ResponseRewrite" defaultRedirect="/_layouts/error2.aspx" />
      
    3. Salvare le modifiche
    4. Eseguire iisreset /noforce

giovedì 2 settembre 2010

SharePoint 2010 - Esempio di ECMA Client Object Model

Ero curioso di provare il nuovo Client Object Model di SharePoint 2010, così ho scritto qualche riga di codice per provare a listare tutti i siti di una site collection. Avendo trovato un po' di difficoltà a fare questa semplice operazione, in quanto ho trovato poco chiare le informazioni presenti nello SDK, ho anche cercato una funzione Javascript che mi consentisse di ispezionare gli oggetti dell'ECMA COM (la funzione l'ho trovato su questo sito).

<script type="text/javascript">
ExecuteOrDelayUntilScriptLoaded(initialize, "sp.js");
var context = null;  
var web = null;
var collWeb = null;

/*function initialize()
{
 context = new SP.ClientContext.get_current();  
 web = context.get_web();
 context.load(web);
 context.executeQueryAsync(Function.createDelegate(this, this.onSuccessMethod), Function.createDelegate(this, this.onFailureMethod));
}

function onSuccessMethod(sender, args) {  
 var d = document.getElementById("list");
 var l = document.createElement("LI");
 l.innerHTML = "<span>" + web.get_title() + "</span>";
 d.appendChild(l);
}*/ 

function initialize()
{
 context = new SP.ClientContext.get_current();  
 this.collWeb = context.get_web().get_webs();
 context.load(this.collWeb);
 context.executeQueryAsync(Function.createDelegate(this, this.onSuccessMethod), Function.createDelegate(this, this.onFailureMethod));
}

function onSuccessMethod(sender, args) {  
 
 var d = document.getElementById("list");
 var web = null;
 for(var x=0; x < this.collWeb.get_count(); x++)
 {
  var l = document.createElement("LI");
  web = this.collWeb.itemAt(x);
  l.innerHTML = "<span>" + web.get_title() + "</span>";
  d.appendChild(l);
 }
 
 var d2 = document.getElementById("test");
 var d1 = document.createElement("DIV");
 //d1.innerHTML = inspect(this.collWeb.itemAt(0),1,0);
 d1.innerHTML = inspect(this.collWeb,1,0);
 d2.appendChild(d1);
} 

 
function onFaiureMethodl(sender, args) {  
     alert('request failed ' + args.get_message() + '\n' + args.get_stackTrace());  
} 

function inspect(obj, maxLevels, level)
{
  var str = '', type, msg;

    // Start Input Validations
    // Don't touch, we start iterating at level zero
    if(level == null)  level = 0;

    // At least you want to show the first level
    if(maxLevels == null) maxLevels = 1;
    if(maxLevels < 1)     
        return '<font color="red">Error: Levels number must be > 0</font>';

    // We start with a non null object
    if(obj == null)
    return '<font color="red">Error: Object <b>NULL</b></font>';
    // End Input Validations

    // Each Iteration must be indented
    str += '<ul>';

    // Start iterations for all objects in obj
    for(property in obj)
    {
      try
      {
          // Show "property" and "type property"
          type =  typeof(obj[property]);
          str += '<li>(' + type + ') ' + property + 
                 ( (obj[property]==null)?(': <b>null</b>'):( '' )) + '</li>';

          // We keep iterating if this property is an Object, non null
          // and we are inside the required number of levels
          if((type == 'object') && (obj[property] != null) && (level+1 < maxLevels))
          str += inspect(obj[property], maxLevels, level+1);
      }
      catch(err)
      {
        // Is there some properties in obj we can't access? Print it red.
        if(typeof(err) == 'string') msg = err;
        else if(err.message)        msg = err.message;
        else if(err.description)    msg = err.description;
        else                        msg = 'Unknown';

        str += '<li><font color="red">(Error) ' + property + ': ' + msg +'</font></li>';
      }
    }

      // Close indent
      str += '</ul>';

    return str;
}
</script>

<div id="test">
Elenco dei siti della site collection listati tramite ECMAScript Client Object 
Model<br /> 
<ul id="list"></ul>
</div>