XSLT med XMLDataSource kontrollen (och GeoNames)

Höll på att testa lite med att koppla en XMLDataSource till en DropDownList och stötte på lite problem. Jag fick följande felmeddelande:

System.Web.UI.WebControls.XmlDataSourceNodeDescriptor innehåller inte någon egenskap med namnet name.

Efter lite sökning på nätet kom jag underfund med att det inte går att ange DataTextField till ett fält-värde i xml-filen. Det ska vara attribut till en tagg för att det ska fungera. Här är ett exempel på den xml fil som jag testade med som för övrigt är genererad av an tjänst på www.geonames.org och visar "first-order administrative division" Sverige. Jag ville få fram alla län men tror inte detta var helt rätt. Resten av parametrarna som man kan skicka med finns på en lista, här.

<geonames>
<totalResultsCount>85543</totalResultsCount>

<geoname>
<name>Stockholm</name>
<lat>59.3294681359869</lat>
<lng>18.0626392364502</lng>
<geonameId>2673730</geonameId>
<countryCode>SE</countryCode>
<countryName>Sweden</countryName>
<fcl>P</fcl>
<fcode>PPLC</fcode>
</geoname>

<geoname>
<name>Gothenburg</name>
<lat>57.7166667</lat>
<lng>11.9666667</lng>
<geonameId>2711537</geonameId>
<countryCode>SE</countryCode>
<countryName>Sweden</countryName>
<fcl>P</fcl>
<fcode>PPLA</fcode>
</geoname>

<geoname>
<name>Uppsala</name>
<lat>59.85</lat>
<lng>17.6333333</lng>
<geonameId>2666199</geonameId>
<countryCode>SE</countryCode>
<countryName>Sweden</countryName>
<fcl>P</fcl>
<fcode>PPLA</fcode>
</geoname>
...
</geonames>

Jag hittade lösningen som jag använder på Raj Kaimal's blogg. För att transformera xml-filen ovan så att name blir ett attribut till geoname kan man använda en XSLT-fil (TransformGeonames.xsl) som ser ut så här.

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="geonames">
<geonames>
<xsl:apply-templates select="geoname"/>
</geonames>
</xsl:template>
<xsl:template match="geoname">
<geoname>
<xsl:attribute name="name">
<xsl:value-of select="name"/>
</xsl:attribute>
</geoname>
</xsl:template>

</xsl:stylesheet>

Slutligen visar jag den koden som konsumerar web-servicen med XSLT-transformeringen.

<%@ Page Language="C#" AutoEventWireup="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>XmlDataSource with XSLT</title>
</head>
<body>
<form id="form1" runat="server">
<asp:dropdownlist ID="Dropdownlist1" DataSourceID="XmlDataSource1" DataTextField="name" runat="server"></asp:dropdownlist>
<asp:XmlDataSource ID="XmlDataSource1" DataFile="http://ws.geonames.org/search?q=sverige&country=SE&fclass=ADM1" TransformFile="TransformGeonames.xsl" XPath="geonames/geoname" runat="server"></asp:XmlDataSource>
</form>
</body>
</html>

By Jesper Lind

ASP.NET AJAX V1.0 Beta

André Henriksson's blogg läser jag att den första betan av ASP.NET AJAX V1.0. Det som innan kallades "Atlas". En väldigt glad nyhet, nu blir det äntligen till att börja prova på allvar med den nya spännande tekniken. Nu ska jag ta och installera det på mina en av mina maskiner med en gång. Releasen består av tre olika delar som går att ladda ner här.
  • "Core-biblioteket" som är basfunktionaliteten som du laddar ner här
  • "Extensions" som är utökningen som kan förändras med olika releaser som du laddar ner här
  • "Toolkit:et" som du laddar ner här

Läs även dokumentationen med instruktioner hur man installerar och använder exemplen.

By Jesper Lind

Formatera valuta med decimaler i GridView

Jag stötte på lite problem när jag skulle formatera valuta i ett templatefield i en GridView. Decimalerna ändrade sig inte alls även fast jag var säker på att jag använt rätt formatering. Talet 0 visade sig som 0,0000 kr. Efter lite googlande hittade jag lösningen på mitt problem på The Ensoft blog. Man ska ange HtmlEncode="false" för att formateringen ska fungera.

Följande exempel visar en formatering med två decimaler.

<asp:BoundField HeaderText="Fraktkostnad" DataField="DeliveryCost" SortExpression= "DeliveryCost" HtmlEncode="false" DataFormatString="{0:c2}" />

Här finns en bra artikel ifall du vill läsa mer om hur man kopplar data med ASP.NET 2.0.

By Jesper Lind

Växelkurser med Web Service del 2

Jag skrev för ett tag sen om hur man kan lägga till en web service till sitt projekt och hämtar ut växelkurser.

Nu tänkte jag prova en annan tjänst från Webservicex.net. Exemplet hittade jag i den danska bloggen Walk the walk.

Instruktioner
Lägg till följande web service och ange net.webservicex.www som namnrymd: http://www.webservicex.net/CurrencyConvertor.asmx

Koden


<%@ Page Language="C#" Trace="false" validateRequest="false" EnableSessionState="True" EnableViewState="true" SmartNavigation="false"%>
<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Web.UI.WebControls" %>
<%@ Import Namespace="net.webservicex.www" %>
<script Language="c#" runat="server">
protected void Page_Load(object sender, EventArgs e)
{

if (!Page.IsPostBack)
{
DataTable dt = this.GetCurrencies();
ddlFromCurrency.DataSource = dt;
ddlFromCurrency.DataBind();
ddlTooCurrency.DataSource = dt;
ddlTooCurrency.DataBind();
}
}

protected void ddlTooCurrency_SelectedIndexChanged(object sender, EventArgs e)
{
if (ddlFromCurrency.SelectedValue.Length > 0)
{
double total = this.GetCurrency() * 100;
lblPrice.Text = total.ToString();
}
}

protected void ddlFromCurrency_SelectedIndexChanged(object sender, EventArgs e)
{
if (ddlTooCurrency.SelectedValue.Length > 0)
{
double total = this.GetCurrency() * 100;
lblPrice.Text = total.ToString();
}
}

private double GetCurrency()
{
CurrencyConvertor cc = new CurrencyConvertor();
return cc.ConversionRate((Currency)Enum.Parse(typeof(Currency), ddlFromCurrency.SelectedValue), (Currency)Enum.Parse(typeof(Currency), ddlTooCurrency.SelectedValue));
}

protected DataTable GetCurrencies()
{
DataTable dtCur = new DataTable("currency");
dtCur.Columns.Add("cur");

foreach (string name in Enum.GetNames(typeof(Currency)))
{
DataRow r = dtCur.NewRow();
r["cur"] = Enum.Parse(typeof(Currency), name);
dtCur.Rows.Add(r);
r = null;
}
return dtCur;
}
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Real time curreny in ASP.NET</title>
<link rel="stylesheet" href="../example.css" type="text/css" />

</head>

<body>
<form runat="server">
<div id="content">
<b>Currency example using web service in ASP.NET</b>
<br /><br />

<table width="30%" border="1" cellpadding="2" cellspacing="0">
<tr>
<td>From currency</td>
<td><asp:DropDownList AutoPostBack="true" DataValueField="cur" DataTextField="cur" ID="ddlFromCurrency" runat="server" OnSelectedIndexChanged="ddlFromCurrency_SelectedIndexChanged" /></td>
</tr>
<tr>
<td>To Currency</td>
<td><asp:DropDownList AutoPostBack="true" DataValueField="cur" DataTextField="cur" ID="ddlTooCurrency" runat="server" OnSelectedIndexChanged="ddlTooCurrency_SelectedIndexChanged" /></td>
</tr>
<tr>
<td>&nbsp;</td>
<td><asp:Label ID="lblPrice" runat="server" /></td>
</tr>
</table>

</div>
</form>
</body>
</html>

Länk till sida som visar kurser i real-tid

Fullständig lista över länder och betäckningar

AFA-Afghanistan Afghani
ALL-Albanian Lek
DZD-Algerian Dinar
ARS-Argentine Peso
AWG-Aruba Florin
AUD-Australian Dollar
BSD-Bahamian Dollar
BHD-Bahraini Dinar
BDT-Bangladesh Taka
BBD-Barbados Dollar
BZD-Belize Dollar
BMD-Bermuda Dollar
BTN-Bhutan Ngultrum
BOB-Bolivian Boliviano
BWP-Botswana Pula
BRL-Brazilian Real
GBP-British Pound
BND-Brunei Dollar
BIF-Burundi Franc
XOF-CFA Franc (BCEAO)
XAF-CFA Franc (BEAC)
KHR-Cambodia Riel
CAD-Canadian Dollar
CVE-Cape Verde Escudo
KYD-Cayman Islands Dollar
CLP-Chilean Peso
CNY-Chinese Yuan
COP-Colombian Peso
KMF-Comoros Franc
CRC-Costa Rica Colon
HRK-Croatian Kuna
CUP-Cuban Peso
CYP-Cyprus Pound
CZK-Czech Koruna
DKK-Danish Krone
DJF-Dijibouti Franc
DOP-Dominican Peso
XCD-East Caribbean Dollar
EGP-Egyptian Pound
SVC-El Salvador Colon
EEK-Estonian Kroon
ETB-Ethiopian Birr
EUR-Euro
FKP-Falkland Islands Pound
GMD-Gambian Dalasi
GHC-Ghanian Cedi
GIP-Gibraltar Pound
XAU-Gold Ounces
GTQ-Guatemala Quetzal
GNF-Guinea Franc
GYD-Guyana Dollar
HTG-Haiti Gourde
HNL-Honduras Lempira
HKD-Hong Kong Dollar
HUF-Hungarian Forint
ISK-Iceland Krona
INR-Indian Rupee
IDR-Indonesian Rupiah
IQD-Iraqi Dinar
ILS-Israeli Shekel
JMD-Jamaican Dollar
JPY-Japanese Yen
JOD-Jordanian Dinar
KZT-Kazakhstan Tenge
KES-Kenyan Shilling
KRW-Korean Won
KWD-Kuwaiti Dinar
LAK-Lao Kip
LVL-Latvian Lat
LBP-Lebanese Pound
LSL-Lesotho Loti
LRD-Liberian Dollar
LYD-Libyan Dinar
LTL-Lithuanian Lita
MOP-Macau Pataca
MKD-Macedonian Denar
MGF-Malagasy Franc
MWK-Malawi Kwacha
MYR-Malaysian Ringgit
MVR-Maldives Rufiyaa
MTL-Maltese Lira
MRO-Mauritania Ougulya
MUR-Mauritius Rupee
MXN-Mexican Peso
MDL-Moldovan Leu
MNT-Mongolian Tugrik
MAD-Moroccan Dirham
MZM-Mozambique Metical
MMK-Myanmar Kyat
NAD-Namibian Dollar
NPR-Nepalese Rupee
ANG-Neth Antilles Guilder
NZD-New Zealand Dollar
NIO-Nicaragua Cordoba
NGN-Nigerian Naira
KPW-North Korean Won
NOK-Norwegian Krone
OMR-Omani Rial
XPF-Pacific Franc
PKR-Pakistani Rupee
XPD-Palladium Ounces
PAB-Panama Balboa
PGK-Papua New Guinea Kina
PYG-Paraguayan Guarani
PEN-Peruvian Nuevo Sol
PHP-Philippine Peso
XPT-Platinum Ounces
PLN-Polish Zloty
QAR-Qatar Rial
ROL-Romanian Leu
RUB-Russian Rouble
WST-Samoa Tala
STD-Sao Tome Dobra
SAR-Saudi Arabian Riyal
SCR-Seychelles Rupee
SLL-Sierra Leone Leone
XAG-Silver Ounces
SGD-Singapore Dollar
SKK-Slovak Koruna
SIT-Slovenian Tolar
SBD-Solomon Islands Dollar
SOS-Somali Shilling
ZAR-South African Rand
LKR-Sri Lanka Rupee
SHP-St Helena Pound
SDD-Sudanese Dinar
SRG-Surinam Guilder
SZL-Swaziland Lilageni
SEK-Swedish Krona
TRY-Turkey Lira
CHF-Swiss Franc
SYP-Syrian Pound
TWD-Taiwan Dollar
TZS-Tanzanian Shilling
THB-Thai Baht
TOP-Tonga Pa'anga
TTD-Trinidad&Tobago Dollar
TND-Tunisian Dinar
TRL-Turkish Lira
USD-U.S. Dollar
AED-UAE Dirham
UGX-Ugandan Shilling
UAH-Ukraine Hryvnia
UYU-Uruguayan New Peso
VUV-Vanuatu Vatu
VEB-Venezuelan Bolivar
VND-Vietnam Dong
YER-Yemen Riyal
YUM-Yugoslav Dinar
ZMK-Zambian Kwacha
ZWD-Zimbabwe Dollar

Omdöme
Tjänsten från WebserviceX.NET verkar väldigt bra. När jag provade SEK till GBP fick jag kursen 7.29 för 100 svenska. Jämnförde med min resaomväxlare och där är kursen just nu 7.25. På x-rates.com är kursen 7.25937. Alla tjänster som jag använt hittills har skiljt sig från andra källor. Varför vet jag inte med marginalerna är ganska små och därför duger det bra till en ungefärlig omräkning.

Nu skulle det vara intressant att sätta ihop det hela med Asp.Net RegionInfo för att automatiskt läsa av vilken kurs som ska hämtas beroende på besökarens inställningar. Men det får bli i en senare artikel.

 

By Jesper Lind

Hur man använder ExpressionBuilder

Ibland känns det naturligt att skriva kod enligt följande:

<asp:Label id="lblUserName" runat="server" Text='<%= CurrentUserName %>' />

Men då får man felet: "Server tags cannot contain constructs".

Infinitiesloop finns det en artikel hur man kan använda ExpressionBuilder att komma runt problemet. Då kan man använda liknande uttyck så här:

<asp:Label id="lblUserName" runat="server" Text=<%$ Code: CurrentUserName %> />

För att detta ska bli möjligt måste man först registrera expressionBuildern i Web.Config:

<compilation debug="true">
    <expressionBuilders>
    <add expressionPrefix="Code" type="CodeExpressionBuilder"/> </expressionBuilders>
</compilation>

Sedan skapar man en klass i App_Code som döps till CodeExpressionBuilder.cs:

using System;
using System.CodeDom;
using System.Web.UI;
using System.ComponentModel;
using System.Web.Compilation;
[ExpressionPrefix("Code")]

public class CodeExpressionBuilder : ExpressionBuilder
{
public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
{
return new CodeSnippetExpression(entry.Expression);
}
}

Då kan man sedan använda uttryck som dessa, direkt i ens kontroller. Coolt!

<%$ Code: DateTime.Now.AddDays(1) %>
<%$ Code: "Hello World, " + CurrentUserName %>
<%$ Code: CurrentUserName.ToUpper() %>
<%$ Code: "Page compiled as: " + this.GetType().AssemblyQualifiedName %>

Mer läsning finns hos Fredrik Normén

By Jesper Lind

Hämta aktuella växelkurser med Web Service

Uppdatering: Web servicen som används i detta exemplet verkar inte finnas kvar längre på nätet.

Inspirerad av en artikel om växelkurser på Code Project har jag gjort ett eget exempel. Jag använder en web service som finns xmethods.net.

Registrera tjänsten
Först måste man använda Visual Studio till att lägga till en referens till servicen.

1. Högerklicka på rooten i Solution Explorern
2. Välj "Add Web Reference"
3. Skriv in url till tjänsten. I vårt fall: http://www.xmethods.net/sd/2001/CurrencyExchangeService.wsdl
4. Skriv in "Web Reference Name". Vi döper den till Rate_WS
5. Då skapas en ny mapp som heter "App_WebReferences" i projektet.

När du har gjort ovanstående så kan man använda tjänsten från sin kod. Här följer ett enkelt exempel som skriver ut några växelkurser.

<%@ Page Language="C#" Trace="true" validateRequest="false" EnableSessionState="True" EnableViewState="true" SmartNavigation="false"%>
<script Language="c#" runat="server">
protected void Page_Load(object sender, EventArgs e)
{
try
{
Rate_WS.CurrencyExchangeService to_currency = new Rate_WS.CurrencyExchangeService();
float Euro_Dollar = to_currency.getRate("euro", "united states");
float Dollar_Sek = to_currency.getRate("united states", "sweden");
float Euro_Sek = to_currency.getRate("euro", "sweden");
float Sek_Yen = to_currency.getRate("sweden", "japan");

lblEuro_Sek.Text = Euro_Sek.ToString();
lblDollar_Sek.Text = Dollar_Sek.ToString();
lblEuro_Dollar.Text = Euro_Dollar.ToString();
lblSek_Yen.Text = Sek_Yen.ToString();
}
catch (Exception) { }
}
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Currency converter in ASP.NET</title>
<link rel="stylesheet" href="../example.css" type="text/css" />

</head>

<body>
<div id="content">
<b>Currency example using web service</b>
<br /><br />

1 US Dollar = <asp:Label ID="lblDollar_Sek" runat="server" /> SEK
<br /><br />
1 Euro = <asp:Label ID="lblEuro_Sek" runat="server" /> SEK
<br /><br />
1 Euro = <asp:Label ID="lblEuro_Dollar" runat="server" /> US Dollar
<br /><br />
1 SEK = <asp:Label ID="lblSek_Yen" runat="server" /> Yen

</div>
</body>
</html>

Länk till sida som visar kurser i real-tid (Exemplet är nedtaget eftersom web servicen inte längre finns på nätet)

Mer läsning:
Läs även den danska bloggen Walk the walk som i ett exempel konsumerar en tjänst på Webservicex.net. Den verkar bra eftersom man anger valutabetäckningen (SEK, USD, DKK osv) som inparameter.

By Jesper Lind

Problem med att hitta kontroll i GridView

Detta är ganska irriterande. Försöker använda ControlParameter för att hitta en DropDownList i min GridView. Men måste då ange det fullständiga Id enligt följande.

<asp:ControlParameter Name="Amount" ControlID="ctl00$mainContentPH$gvBasket$ctl03$ddlAmount" PropertyName="SelectedValue" />

Då kan jag bara komma åt en viss rad, i exemplet ovan den som har index 3. Detta är ju fullkomligt idiotiskt, det måste finnas en lösning. Här finns en artikel där en av läsarna skriver om samma problem i kommentarerna.

En alternativ lösning
Jag har inte kommit på hur man kan göra med problemet ovan. Det är nog helt enkelt så naming containers fungerar i Asp.Net. Istället gjorde jag en metod som hittar DropDownListan och lägger till parametern från koden bakom.

protected void gvBasket_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
GridViewRow row = gvBasket.Rows[e.RowIndex];
DropDownList ddlAmount = row.FindControl("ddlAmount") as DropDownList;
string strAmount = ddlAmount.SelectedValue.ToString();
sdsBasket.UpdateParameters.Add("Amount", strAmount);
}

By Jesper Lind

Använda Repeater istället för Menu komponenten

Jag har börjat använda Web.sitemap flitigare i mina applikationer. asp:Menu komponenten är ju smidig eftersom den är så lätt att binda till en SiteMapDataSource och menyn ritas fram automatiskt.

Men jag stötte dock på en del problem som jag inte vet hur man kan lösa. För det första genererar den onödigt mycket kod med mängder av nästlade tabeller och det är väldigt svårt att lista ut hur man ska få den att uppföra sig precis som man vill.

Sen ville jag ha streckade linjer som separatorer och använde StaticBottomSeparatorImageUrl för att ange min bild. Kunde dock inte komma på hur man kan göra för att bara ha dem imellan menylänkarna. Inte före eller efter med andra ord.

Efter ett stunds sökning på nätet gav jag upp. Hittade istället en djupgående artikelserie på www.devx.com där nio problem med sajt navigation tas upp. Där hittade jag ett tips om hur man kan binda ihop en Repeater till sin SiteMapDataSource istället för Menu kontrollen. Men hjälp av Repeatern kunde jag placera separator koden där jag ville samt att få hela menyn att renderera snyggare utan massa överflödiga tabeller.

Repeatern

<table width="100%" cellpadding="0" cellspacing="5px">
<tr>
<td class="menuBack">

<asp:Repeater ID="repeaterMenu" runat="server" DataSourceID="smds" OnItemDataBound="repeaterMenu_ItemDataBound">
<ItemTemplate>
<asp:PlaceHolder ID="phMenuSeparator" runat="server" />
<a class="menu" href='<%# String.Format( ((SiteMapNode)Container.DataItem).Url)%>' target='_top'><%# String.Format( ((SiteMapNode)Container.DataItem).Title)%></a>
</ItemTemplate>
</asp:Repeater>
</td>
</tr>
</table>

<asp:SiteMapDataSource ID="smds" ShowStartingNode="False" runat="server" />

Koden bakom

//Räknare för att hålla reda på vilken länk man är på i menyn
int intCounter = 0;

public void repeaterMenu_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
try
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{

//Lägg bara till separator ifall efter vi passerat första meny objektet
if (intCounter > 0)
{
PlaceHolder phMenuSeparator = (PlaceHolder)e.Item.FindControl("phMenuSeparator");
phMenuSeparator.Controls.Add(new LiteralControl(" &lt;div class='menuSeparator' &gt;&lt;/div&gt;"));
}
intCounter++;
}
}
catch (Exception objException)
{
Trace.Write("repeaterMenu_OnDataBinding() Fel!", objException.Message);
}
}

Css

.menuBack
{
border:1px solid #cccccc;
background-image: url('../images/menu_back.gif') ;
background-repeat:repeat-x;
background-position:top;
height:1.6em;

}

.menuBackSelected
{

background-image: url('../images/menu_back_selected.gif') ;
background-repeat:repeat-x;
background-position:top;
display:block;
float:left;
height:110%;
margin:0;
color:#000;
}

.menuSeparator
{
background-image: url('../images/menu_separator.gif') ;
background-repeat:repeat-y;
display:block;
float:left;
margin:0;
height:110%;
width:1px;

}


a.menu
{
padding: 0.2em 10px 0.2em 10px;
font-weight:bold;
text-decoration: none;
color: #fff;
display:block;
float:left;
}

a.menu:link
{
color: #fff;
}

a.menu:visited
{
color: #fff;
}

a.menu:hover
{

color: #fff;
text-decoration: underline;}

a.menu:active
{
color: #fff;
text-decoration: underline;}

a.menuselected
{
padding: 0.2em 10px 0.2em 10px;
font-weight:bold;
color: #000;
text-decoration: none;
display:block;
float:left;
}

a.menuselected:link
{
color: #000;
}

a.menuselected:visited
{
color: #000;
}

a.menuselected:hover
{
text-decoration: underline;}

a.menuselected:active
{
text-decoration: underline;}

By Jesper Lind