di Enrico SabbadinFate un Web Service che riceve una stringa e restiuisce una stringa.
Fate si che l'implementazione del web service restituisca System.Environment.NewLine (la sequenza /r/n).
Fate quindi un chamante del web service e fate si che il chiamante passi anch'esso System.Environment.NewLine.
Lanciate in debug e vedete cosa succede : da entrambi i lati riceventi la sequenza /r/n viene trasformata in /n .. come mai ?
Il fatto è che c'è una specifica dell'XML che dice che una tale sequenza deve essere trasformata (normalizzata) in /n (specificatamente , la sequenza di caratteri asci 13 , 10 deve essere trasformata in 10.
Il responsabile della sparizione del /r è il lato ricevente: di default i web services usano un xmltextreader con la proprietà Normalization impostata a true (provate a serializzare e quindi deserializzare in maniera esplcita un oggetto avente una proprietà stringa impostata con un /r/n usando l'XMLSerializer a cui passate un xmltextreader con la proprietà Normalization impostato prima a true e poi false).
La sparizione della sequenza /r/n puo' essere un bel problema per molte applicazioni.
Per fortuna esiste una soluzione : Per evitare che venga persa la sequenza nella risposta del Web Service occorre intervenire sul proxy lato client andando in override del metodo GetReaderForMessage nel modo seguente:
protected override System.Xml.XmlReader GetReaderForMessage(
System.Web.Services.Protocols.SoapClientMessage message, int bufferSize) {
System.Xml.XmlReader l_tmp = base.GetReaderForMessage(message, bufferSize);
((System.Xml.XmlTextReader)l_tmp).Normalization = false;
return l_tmp;
}
(ovviamente non fatelo sul proxy generato dinamicamente , derivate da quello)
Lato server la cosa è piu' articolata : Create una classe custom che deriva da SoapServerProtocolFactory. Tale classe deve restituire un'altra vostra classe custom che deriva da SoapServerProtocol public class MyWSFactory :
System.Web.Services.Protocols.SoapServerProtocolFactory {
protected override System.Web.Services.Protocols.ServerProtocol CreateIfRequestCompatible(
System.Web.HttpRequest request) {
if (request.PathInfo.Length > 0) {
return null;
}
if (request.HttpMethod != "POST") { // need reflection to create the internal UnsupportedRequestProtocol class
Type l_t = Type.GetType
("System.Web.Services.Protocols.UnsupportedRequestProtocol, System.Web.Services, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a", true);
Object l_tmp = l_t.InvokeMember(null, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.CreateInstance, null, null, new object[] { 0x195 });
return (System.Web.Services.Protocols.ServerProtocol)l_tmp ;
// return new UnsupportedRequestProtocol(0x195);
}
return new MyWS();
}
Ed ecco l'implementazione della SoapServerProtocol custom in cui si puo' ottenere una referenza all'XMLTextReader utilizzato :
public class MyWS : System.Web.Services.Protocols.SoapServerProtocol {
protected override System.Xml.XmlReader GetReaderForMessage(
System.Web.Services.Protocols.SoapServerMessage message, int bufferSize) {
System.Xml.XmlReader l_tmp = base.GetReaderForMessage(message, bufferSize);
((System.Xml.XmlTextReader)l_tmp).Normalization = false;
return l_tmp;
}
}
Come ultimo passo dovete aggiungere nel web .config l'entrata seguente per dire ad ASP.NET di agganciare il vostro soapServerProtocolFactory ...
<configuration>
<system.web>
<webServices>
<soapServerProtocolFactory type ="WSFactory.MyWSFactory, WSFactory" />
Poteva essere + facile .. ma per adesso le cose stanno cosi' :)