Archivi tag: Asp.Net

Estendere il WebClient per gestire un CookieStore

In C# , le system.net mettono a disposizione un oggetto chiamato WebClient per fare delle chiamate http in post a risorse identificate da un URI.

Nel mio caso l’ho utilizzato per fare delle chiamate dal CodeBehind di un applicazione web, per interrogare dei servizi web esposti in maniera restfull che accettavano dati in POST.

Dovendo gestire la sessione con questi servizi di backend e a volte avendo la necessità di dovergli pasare dei Cookie di autenticazione o altri cookie , mi sono trovato a doverlo estendere (per non lavorare direttamente con il WebRequest, oggetto su cui poggia il WebClient).

Il webclient offre pochissime funzionalità, ma estenderlo è molto facile.

In questo esempio aggiungiamo la possibilità di creare un proprio CookieStore, prima di una chiamata, in modo da potergli passare tutti i cookie (presi ad esempio dalla Request arrivata alla nostra pagina aspx) necessari al servizio che risponde all’URI interrogata.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using System;

using System.Net;

using System.Web;

public class ExtendedWebClient : WebClient

{

    private CookieContainer m_container = new CookieContainer();

    protected override WebRequest GetWebRequest(Uri address)

    {

        WebRequest request = base.GetWebRequest(address);
        HttpWebRequest webRequest = request as HttpWebRequest;
        if (webRequest!=null){
            webRequest.CookieContainer = m_container;
        }
        return request;
    }

    public void SetCookies(HttpCookieCollection collection,string domain){
        m_container = new CookieStore();
        for (int j=0;j<collection.Count;j++){
            HttpCookie httpCookie = collection.Get(j);
            Cookie cookie = new Cookie();
            cookie.Domain = domain;
            cookie.Expires = httpCookie.Expires;  
            cookie.Name = httpCookie.Name;
            cookie.Path = httpCookie.Path;
            cookie.Secure = httpCookie.Secure;
            cookie.Value = httpCookie.Value;
            m_container.Add(cookie);
        }
    }
}

Oltre a gestire il cookieStore, dobbiamo preoccuparci della conversione dei cookies da HttpCookie (arrivati dalla Request) a quelli più dettagliati della System.Net.

Per utilizzare il nostro nuovo webclient e passargli i cookie possiamo fare in questo modo:

1
2
3
4
5
    ExtendendWebClient client = new ExtendedWebClient();
    client.SetCookies(request.Cookies,request.Url.Host);
    byte[] data = client.DownloadData("http://www.google.it");
    System.Text.Encoding enc = System.Text.Encoding.UTF8;
    string output = enc.GetString(data);

In questo modo tutti i cookie vengono impostati nel webclient prima di eseguire la webRequest.

Giocando con il parametro Domain, possiamo andare a filtrare i cookie che ci interessano per la chiamata in questione.

Problemi di deserializzazione array in c#

Un caso semplicissimo,può diventare un incubo per diverse ore…

Un xml di questo tipo:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<Applicazioni>
<Applicazione>
<NomeApplicazione>XXX1</NomeApplicazione>
<Stato>Attiva</Stato>
</Applicazione>
<Applicazione>
<NomeApplicazione>XXX2</NomeApplicazione>
<Stato>Attiva</Stato>
</Applicazione>
</Applicazioni>

L’utilizzo di una classe per deserializzare il contenuto di questo xml in un oggetto, in c# , diventa difficile a causa della natura del Root Item di questo xml, “Applicazioni”, che risulta essere un Array di “Applicazione” .

Per poter lavorare in maniera corretta con gli attributi XmlArray e XmlArrayItem, senza stare a romperci la testa, aggiungiamo un nodo contenitore ad “Applicazioni”, come nell’esempio seguente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>

<ServizioStato>
<Applicazioni>
<Applicazione>
<NomeApplicazione>XXX1</NomeApplicazione>
<Stato>Attiva</Stato>
</Applicazione>
<Applicazione>
<NomeApplicazione>XXX2</NomeApplicazione>
<Stato>Attiva</Stato>
</Applicazione>
</Applicazioni>

</ServizioStato>

In questo modo possiamo andare a definire un oggetto per la deserializzazione cosi definito :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using System.Xml.Serialization;

namespace ServizioStato
{

[Serializable()]
[XmlRoot("ServizioStato",Namespace="",IsNullable=false)]
public class ServizioStatoApplicazioni
{
private Applicazione[] applicazioni;

[XmlArray("Applicazioni")]
[XmlArrayItem("Applicazione", typeof(Applicazione))]
public Applicazione[] StatoApplicazioni
{
get { return this.applicazioni; }
set { this.applicazioni = value; }
}

}

[Serializable]
public class Applicazione
{
private string nomeApplicazioneField;
private string statoField;

[System.Xml.Serialization.XmlElementAttribute("Applicazione", Namespace="",Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string Applicazione
{
get { return this.nomeApplicazioneField; }
set { this.nomeApplicazioneField = value; }
}
[System.Xml.Serialization.XmlElementAttribute("Stato", Namespace="",Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string Stato
{
get { return this.statoField; }
set { this.statoField = value; }
}
}

Una cosa su cui riflettere è che il tool XSD per la generazione degli oggetti, non ci viene in aiuto con delle strutture definite come il primo esempio (prima dell’aggiunta del nodo ServizioStato), non riuscendo a generare le classi in maniera corretta.

 

Consumare Wcf con configurazione programmatica (senza toccare il web.config)

Le Windows Communication Foundation permettono una configurazione molto semplice e efficace, risolvendo in automatico i binding e tutte le configurazioni di channel, porte e quanto altro direttamente leggendo da web.config oppure da un app.config.

Il problema nasce quando andiamo a utilizzare ad esempio un client verso un servizio wcf , direttamente in delle application pages o webpart all’interno di Sharepoint.

In questo caso la configurazione può essere gestita in 3 modi:

Nel primo caso dobbiamo mettere mano al web.config di Sharepoint, in quanto le application pages e/o le webpart sono hostate all’interno della sua Web Applicaiton, e questo per motivi di policy, non sempre è possibile.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<system.serviceModel>

<bindings>

<basicHttpBinding>

<binding name="RichiestaDatiSoapBinding" closeTimeout="00:01:00"

openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"

allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"

maxBufferSize="2147483647" maxBufferPoolSize="524288" maxReceivedMessageSize="2147483647"

messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"

useDefaultWebProxy="true">

<readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="16384"

maxBytesPerRead="4096" maxNameTableCharCount="16384" />

<security mode="None">

<transport clientCredentialType="None" proxyCredentialType="None"

realm="" />

<message clientCredentialType="UserName" algorithmSuite="Default" />

</security>

</binding>

</basicHttpBinding>

</bindings>

<client>

<endpoint address="http://localhost:81/testservice/servizio.asmx" binding="basicHttpBinding" bindingConfiguration="RichiestaDatiSoapBinding"

contract="TestService.RichiestaDatiSoapBinding" name="RichiestaDatiSoapBinding" />

</client>

</system.serviceModel>

Il secondo meccanismo , messo a disposizione direttamente dalle Api di Sharepoint, prevede l’utilizzo delle SPWebConfigModification Api, che permettonod i manipolare il web.config di iis, mediante codice, andando ad aggiungere le parti che servono in fase , ad esempio, di attivazione di una feature.. A mio avviso questa parte è qualcosa di veramente terrificante, perche si arriva ad ottenere un web.config spesso illeggibile e quasi sempre non funzionante e oltretutto difficile da controllare visto che tutto il value che deve essere passato al configModification deve avere l’escape di tutti i caratteri particolari.. quindi pieno di entity xml come il @quot …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SPWebConfigModification modificaWebConfig= new SPWebConfigModification();

modificaWebConfig.Path = "configuration/system.serviceModel";

modificaWebConfig.Sequence = 0;

modificaWebConfig.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;

modificaWebConfig.Owner = System.Threading.Thread.CurrentPrincipal.Identity.Name;

modificaWebConfig.Value = @"<bindings><basicHttpBinding>..etc.etc..inserire qui tutto l'xml del binding";

SPWebService service = SPWebService.ContentService;

service.WebConfigModifications.Add(modificaWebConfig);
service.Update();
service.ApplyWebConfigModifications();

Un terzo modo è quello di creare una classe che fornisca direttamente il Binding (BasicHttpBinding) e l’Endpoint (EndPointAddess).

Purtroppo per motivi di sicurezza , microsoft ha bloccato la possibilità di leggere l’xml e deserializzarlo nelle relative classi messe a disposizioni dal Framework, quindi la creazione del binding va fatta “a mano”.

I dati possono essere statiticizzati nel codice o ad esempio letti da una Lista di Sharepoint, in cui magari andiamo a memorizzare in un unico Item tutto il blocco xml della configurazione del binding .

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class WCFClientConfiguration
{

public BasicHttpBinding Binding
{
get
{
BasicHttpBinding _binding = new BasicHttpBinding()
{
Name = "ServizioTestSoap",
CloseTimeout = new TimeSpan(1, 5, 0),
OpenTimeout = new TimeSpan(1, 5, 0),
ReceiveTimeout = new TimeSpan(1, 10, 0),
SendTimeout = new TimeSpan(1, 5, 0),
AllowCookies = false,
BypassProxyOnLocal = true,
HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
MaxBufferSize = 600000,
MaxBufferPoolSize = 524288,
MaxReceivedMessageSize = 600000,
MessageEncoding = WSMessageEncoding.Text,
TextEncoding = System.Text.Encoding.UTF8,
TransferMode = TransferMode.Buffered,
UseDefaultWebProxy = false,
ReaderQuotas = new XmlDictionaryReaderQuotas()
{
MaxDepth = 32,
MaxStringContentLength = 500000,
MaxArrayLength = 16384,
MaxBytesPerRead = 4096,
MaxNameTableCharCount = 16384
}

};

_binding.Security.Mode = BasicHttpSecurityMode.Transport;

return _binding;
}
}

public EndpointAddress Endpoint
{
get
{
return new EndpointAddress("https://localhost:81/testService/service.asmx");
}
}
}

 

In questo modo quando andiamo a instanziare il nostro client, usiamo il costruttore che accetta in input Binding e Endpoint.

1
2
3
WcfConfiguration conf = new WCFClientConfiguration();

ServizioTestSoapClient client = new ServizioTestSoapClient (conf.Binding,conf.Endpoint);

Questo permette la memorizzazione ad esempio della configurazione su DB o su altri sistemi di persistenza .