Första dagarna med VS 2008 och .NET 3.5 Beta 2

Julafton kom tidigt för oss .NET-utvecklare i år. Microsoft höll tidschemat och levererade VS 2008 med .NET 3.5 Beta 2.

Men helt smärtfritt var det inte att få igång det för mig. Eftersom jag hade läst att denna version skulle fungerar bra vid sidan om VS 2005 satte jag igång installationen. Denna avbröts dock mitt i pga något fel, och lämnade mig med VS 2005 och hela min utvecklingsmiljö kaputt. Det beror nog på att jag redan hade en del fel på mina installationer, men en liten varning alltså. Installera den inte på din produktionsmijö om du vill slippa problem.

En ominstallation av XP senare, så gick det mycket bättre och nu rullar det fint på min maskin. Än har jag bara känt på det nya utvecklingpaketet, men bara en sån sak som Intellisense på Javascript har gjort det mödan värt.

VS 2008 med ASP.NET AJAX 1.0

Något som är lite lurigt är att få Ajax att fungera i NET2.0 läget på VS 2008. Först måste man se till att köra ett skript för att inte VS 2008 automatiskt ska använda de nya inbyggda Ajax-referenserna.

Sedan är det några fler steg som måste genomföras för att få fram Ajax-kontrollerna i toolboxen och ladda in referenserna. Microsofts utvecklingsteam har gjort en grundläggande förklaring för hur man gör.

By Jesper Lind

Referera till cachade kontroller som laddats in dynamiskt

Vi har tidigare gått igenom hur man refererar till instanser av User Controls i koden.

Om dessa kontroller är cachade så blir det lite svårare. Speciellt om man ska kunna sätta några properties på dem. Via OdeToCode hittade jag info hos Microsoft om hur man kan göra, och även exempelkod.

// Load the user control
Control control = LoadControl("control/MyControl.ascx");
placeHolder.Controls.Add(control);

// Set its properties (if possible)
ASP.MyControl uc = control as ASP.MyControl;
if (uc == null)
{
PartialCachingControl pcc = control as PartialCachingControl;
if (pcc != null) uc = pcc.CachedControl as ASP.MyControl;
}
if (uc != null) uc.MyProperty = "This is the value";

Ps. I mitt exempel har jag med ASP-prefixet på klassen för kontrollen. Detta beror på att jag använder inline-code, läs mer om detta i tidigare nämnt inlägg.

By Jesper Lind

UpdatePanel - TinyMCE demo med nedladdningsbar zip-fil

TinyMCE är en skön HTML-Editor som har blivit väldigt populär. Den är oftast väldigt enkel att använda och har många konfigureringsmöjligheter.

Men att använda den tillsammans med Ajax och partial update är verkligen ingen lätt match. Jag lyckades tidigare i år, efter stor möda, men det var nog mest tur.

Skrev om det i bloggen, men den postningen blev inte så fullständig eller bra. Det var mest lite egna minnesanteckningar som jag fick till där. Jocke var inne och kollade på beskrivningen men fick inte till det. Så nu tänkte jag visa precis hur man kan göra med ett uppdaterad och fullständig kod.

Problemen

Problem 1. TinyMCE gör om textboxar till en Iframe, som My Portal Project förklarar. Detta leder till att det är svårt att ta emot värden (speciellt i Firefox). Waqas_badas visar hur man kan komma runt detta genom ett javascript på submit-knappen.

Problem 2. Vid Ajax-anrop ritas inte editorn upp igen. Här kan man anropa mceAddControl-kommandot, men det gäller att resetta id-räknaren innan man gör det, här är en som kommit på hur man gör.

Kodexempel

Nu tänkte jag låta koden förklara resten. Missa inte att ladda hem zip-filen längst ner som innehåller ett färdigt project som fungerar i IE7, FF2 och Opera.

Default.aspx

<%@ Page Language="C#" ValidateRequest="false" Trace="false" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>MS Ajax UpdatePanel - TinyMCE</title>
<script type="text/javascript">
function SaveMyPreciousValues()
{
tinyMCE.triggerSave(false,true);
TextBox1 = document.getElementById('TextBox1');
alert('Check value when posting: '+ TextBox1.value)
}
</script>
</head>
<body>

<form id="form1" runat="server">
<asp:ScriptManager ID="sm" EnablePartialRendering="true" runat="server">
</asp:ScriptManager>

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<p>
<asp:Label ID="Label1" runat="server" Text="Welcome to the MS Ajax UpdatePanel - TinyMCE Demo, Enjoy!"></asp:Label>
</p>
<asp:TextBox ID="TextBox1" Rows="10" Columns="50" TextMode="MultiLine" EnableViewState="false" Text="Write something here..." runat="server"></asp:TextBox>

<p>
<asp:Button ID="Button1" runat="server" OnClick="Button1_OnClick" OnClientClick="SaveMyPreciousValues();" Text="Hit me" />
</p>
</ContentTemplate>
</asp:UpdatePanel>

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

Default.cs.aspx

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if(!Page.IsPostBack)
LoadTinyMCE();
}

private void LoadTinyMCE()
{

//Load tinyMCE
HtmlGenericControl Include = new HtmlGenericControl("script");
Include.Attributes.Add("type", "text/javascript");
Include.Attributes.Add("src", "js/tiny_mce/tiny_mce.js");
this.Page.Header.Controls.Add(Include);


//Config MCE
HtmlGenericControl Include2 = new HtmlGenericControl("script");
Include2.Attributes.Add("type", "text/javascript");
Include2.InnerHtml = "tinyMCE.init({mode : 'textareas' ,language : 'sv',entity_encoding : 'raw'});";
this.Page.Header.Controls.Add(Include2);

}

protected void Button1_OnClick(object sender, EventArgs e)
{

//Get the contect of the TextBox
string inputText = Request.Form["TextBox1"];

//Print all Form-values when testing
/*for (int i = 0; i<Request.Form.Count;i++ )
{
string itemName = Request.Form.AllKeys[i];
string itemValue = Request.Form.GetValues(i)[0];
Label1.Text = Label1.Text + "<br />" + itemName + ":" + itemValue;
}*/

Label1.Text = "Content posted from TextBox1: " + inputText;

//Register some javascript to redraw the editor.
//Very important to reset the id-counter to "0", or else strange things will happen..
ScriptManager.RegisterClientScriptBlock(UpdatePanel1, this.GetType(), "init", "tinyMCE.idCounter=0;tinyMCE.execCommand('mceAddControl', false, 'TextBox1');", true);
}
}

zip-icon UpdatePanelTinyMCE.zip (C#)

Uppdatering: Joakim har skrivit om exemplet till VB.NET, läs mer på hans blogg.

By Jesper Lind

Layoutlösningar med nästade Masterpages

Den nuvarande versionen av Visual Studio stödjer inte nästade Masterpages i designläget. Men detta kommer det bli ändring på i den kommande VS 2008 som ScottGu förklarar.

Scott visas samtidigt på att visa en användbar teknik, för att skapa olika layouts, för sidor som har en kolumn respektive två.

Det ryktas för övrigt att Visual Studio 2008 Beta 2 kommer släppas inom tre veckor och då med en "go-live"-licens.

By Jesper Lind

CSS Friendly Control Adapters

Nu har jag testat att använda CSS Friendly Control Adapters som jag nämde förra året. De fungerar verkligen magiskt bra och man får en mycket mer CSS-vänlig HTML-kod från kontrollerna i Asp.Net.

Hur man installerar adaptrarna

Det finns två olika sätt att lägga till adaptrarna som även CodeClimber förklarar.

Dels det lite omständiga sättet som Microsoft har utvecklat, här måste man lägga till speciella javascript och CSS bibliotek. Då får man dock större kontroll över koden om man vill modifiera den själv.

Vill man göra det enklare för sig så kan man använda Brian DeMarzo's variant som nu finns som ett eget projekt på CodePlex. Microsoft har lämnat sina över hela projektet till CodePlex så det är där som utvecklingen kommer att ske i framtiden.

Instruktionerna är enkla och görs genom tre steg:

  1. Ladda hem det senaste filerna.
  2. Lägg till en referens till CSSFriendly.dll i din webb-applikation.
  3. Lägg till CSSFriendlyAdapters.browser konfigurationen till App_Browsers mappen.

Kan ställa till problem med ViewState

När jag använde de vanliga adaptrarna (inte CSSFriendly.dll) Stötte dock inledningsvis på ett error:

The Controls collection cannot be modified because the control contains code blocks (i.e. <% ... %>).

Är inte säker på vad det berodde på, men hittade en lösning i en tråd på .NET-forumet.

Problemet var i TreeView och efter jag la in följande kod så fungerade det.

private string _strviewState = string.Empty;

private string ViewStateID
{
get { return Control.ClientID + "__VIEWSTATE"; }
}

...

protected override Object SaveAdapterViewState()
{
string retStr = "";
TreeView treeView = Control as TreeView;
if ((treeView != null) && (_viewState != null))
{
if ((_viewState != null) && (Page != null) && (Page.Form != null))
{
Page.ClientScript.RegisterHiddenField(ViewStateID, _strviewState);
string script = "document.getElementById('" + ViewStateID + "').value = GetViewState__AspNetTreeView('" + Extender.MakeChildId("UL") + "');";
Page.ClientScript.RegisterOnSubmitStatement(typeof(TreeViewAdapter), ViewStateID, script);
}
retStr = ViewStateID + "|" + ComposeViewState(treeView.Nodes, "");
}
return retStr;

Observera att jag koden skiljer sig lite från den i forum-tråden. Så jag kan inte lova att viewstate verkligen fungerar med denna modifikation. I mitt fall behöver jag inget viewstate så jag nöjer mig där.

By Jesper Lind

Hotfix för problem med valideringsfunktioner i .NET Ajax

Validerings-funktionerna i .NET har haft en del problem genom versionerna. I en version (har för mig att det var .NET 1.1) fungerade de knappt över huvudtaget. Men i 2.0 hade Microsoft löst de första problemen.

När sen MS Ajax släpptes blev det återigen en del buggar och problem. Det märkte jag häromdan när jag försökte ha validering på en TextBox i en wizard-kontroll som i sin tur låg inom en UpdatePanel. På första steget hade jag en TextBox med validering och där fungerade den fint. På andra steget så låg javascript-funktionerna kvar utanför UpdatePanel, men textboxen hade ju redan försvunnit och då blev det error som såg ut så här:

control has no properties
ValidatorGetValue("ctl00_mainContentPH_wizard_tbOrderId")WebResource.axd (line 104)
RequiredFieldValidatorEvaluateIsValid(span#ctl00_mainContentPH_wizard_ReqFieldOrderId)WebResource.axd (line 475)
ValidatorValidate(span#ctl00_mainContentPH_wizard_ReqFieldOrderId, "", null)WebResource.axd (line 208)
Page_ClientValidate("")WebResource.axd (line 128)
WebForm_DoPostBackWithOptions(Object)WebResource.axd (line 14)
if (typeof(control.value) == "string") {
 
Microsoft har dock gjort sitt bästa för att lösa även detta problem. Först så rekommenderades att man laddade ner en Validators.dll och mappade upp mot i Web.config.
 
Genom en kommentar hos Guttrie hittade jag info om en ny hotfix. Enligt erfarenhet när jag patchade ett Windows 2003-system fungerar den väldigt bra och man slipper att gör några ändringar i Web.config.
 
Om du inte orkar trixa för mycket med det och kan vänta så kommer en Windows Update snart med lösning.  

By Jesper Lind

Virtuellt RSS-flöde med .NET

Vi har tidigare har vi på bloggen visat hur man kan skapa en RSS-fil från poster i en databas.

Nu tänkte jag visa ett snarlikt exempel men här skriver vi flödet direkt till Response-strömmen istället för att skapa en fysisk fil. Kallar denna metoden, att leverera ett RSS-flöde "virtuellt", i brist på bättre term.

Hämtade lite inspiration från Dileno.com men använde istället min egen kod från tidigare exempel.

Själva ASPX-filen sparar jag som Rss.aspx och innehåller endast en rad kod.

<%@ Page Language="C#" Strict="False" Debug="True" Inherits="_Rss" CodeFile="Rss.aspx.cs" %>

I koden bakom hämtas posterna ur en tabell i en Access-databas. Bygger ihop en sträng med xml taggarna för RSS-definitionen och lägger sedan in den i cache. Detta för att det inte ska bli överbelastning ifall flödet anropas ofta.

using System;
using System.Data;
using System.Data.OleDb;
using System.Configuration;
using System.Text;
using System.Web;
using System.IO;
using System.Xml;

public partial class _Rss : System.Web.UI.Page
{
static string strConn = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
static string strRootUrl = "http://codeodyssey.se";

protected void Page_Load(object sender, EventArgs e)
{
Response.ContentType = "text/xml";
Response.ContentEncoding = Encoding.UTF8;
Response.Write(GetRssString());
}

//Kollar om det redan finns en sträng i cache, annars skapas en ny
protected string GetRssString()
{
string cacheName = "rss-feed";

//Hämta chachad data om det finns
if (Cache[cacheName] != null)
{
return Convert.ToString(Cache[cacheName]);
}
else
{
string newlyGeneratedRss = GenerateRss();
Cache.Insert(cacheName, newlyGeneratedRss, null, DateTime.Now.AddMinutes(20), TimeSpan.Zero);
return Convert.ToString(newlyGeneratedRss);
}
}

//Funktion som skapar en textSträng som innehåller rss-feed
protected string GenerateRss()
{
StringWriter sw = new StringWriter();
try
{

XmlTextWriter xtw = new XmlTextWriter(sw);//,System.Text.Encoding.GetEncoding("UTF-8")
xtw.Formatting = Formatting.Indented;
xtw.WriteStartDocument();

//skriv ut <rss version="2.0">
xtw.WriteStartElement("rss");
xtw.WriteAttributeString("version","2.0");

//skriv ut <channel>
xtw.WriteStartElement("channel");

//skriv ut element som tillhör <channel>
xtw.WriteElementString("title","Codeodyssey.se");
xtw.WriteElementString("link","http://www.codeodyssey.se/");
xtw.WriteElementString("description","Code Odyssey - gräver djupt i kodträsket");
xtw.WriteElementString("language","sv-SE");
xtw.WriteElementString("copyright","Copyright (c) 2004-2007 Code Odyssey");

OleDbConnection conn = new OleDbConnection (strConn);
bool boolPermission = false;
OleDbDataReader objDataReader=null;
try
{
string strSQL = "SELECT TOP 10 * FROM News WHERE Published=true ORDER BY PublishDate DESC";

conn.Open();

OleDbCommand objCommand = new OleDbCommand(strSQL, conn);
objDataReader = objCommand.ExecuteReader();
while (objDataReader.Read() == true)
{
int Id = Convert.ToInt32(objDataReader["Id"]);
string Title = Convert.ToString(objDataReader["Title"]);
string Body= Convert.ToString(objDataReader["Body"]);
//Se till att datum följer RFC-822 standard
string PublishDate = Convert.ToString( ((DateTime)objDataReader["PublishDate "]).ToString("r"));


//skriv ut <item> och dess innehåll
xtw.WriteStartElement("item");
xtw.WriteElementString("title",Title);
xtw.WriteElementString("link", strRootUrl.ToLower() + "/news.aspx?id=" + Id);
xtw.WriteElementString("guid", strRootUrl.ToLower() + "/news.aspx?id=" + Id);
xtw.WriteElementString("description",Body);
xtw.WriteElementString("pubDate",PublishDate);
xtw.WriteEndElement();
}
objCommand.Dispose();
objCommand = null;
}
catch (Exception objException)
{
Trace.Warn("GenerateRss() Fel!",objException.Message);
}
finally
{
if(objDataReader!=null)
{
objDataReader.Close();
objDataReader = null;
}
if(conn!=null)
{
conn.Close();
conn = null;
}
}

//skriv ut </channel>
xtw.WriteEndElement();

//skriv ut </rss>
xtw.WriteEndElement();

//Stäng xml skrivaren
xtw.Close();
}
catch (Exception objException)
{
Trace.Warn("GenerateRss() Fel!",objException.Message);
}
return sw.ToString();
}

}

By Jesper Lind

Verktyg för att analysera loggfiler

När man söker igenom stora textfiler som t ex loggfiler kan det vara bra att ha något specialverktyg till sin hjälp. Det finns säkert massor av såna här program, men tänkte skriva om två stycken som jag provat.

Microsoft Log Parser 2.2 verkar bra, speciellt eftersom den är gjord för att hantera Microsofts egna format på loggfilerna. Tyvärr sker input genom ett CMD-fönster och därför lite väl omständigt att använda enligt mig.

Kollade istället på ett enklare alternativ. Delay släppte igår första publika versionen av sitt verktyg som heter TextAnalysisTool.NET. Har precis testkört det och gillar det verkligen. Genom ett enkelt gränssnitt kan man ladda in loggfiler och sedan välja ut vissa rader man vill granska mer utförligt genom att ange Regular Expressions. 

By Jesper Lind

XHTML Strict med .NET

Här gör jag en liten sammanfattning om tips för hur man får en .NET-applikation att validera som XHTML Strict.

Så testar man 

För att avgöra om sina sidor validerar mot den webbstandard som man valt att följa så kan man använda valideringsverktyget från WC3.

Vill man vara extrem så kan man ju även testa den nya betaversionen. Märkte just en skillnad från den vanliga versionen. I nya betan måste ange rätt namnrymd i html-taggen för att validera som Strict, så här alltså:

<html xmlns="http://www.w3.org/1999/xhtml">

Ställ in Web.config 

Använder man .NET som utvecklingsplattform så finns det några saker man bör ha med i <system.web>-delen av Web.config för att slippa konstiga fel.

Dels så bör man lägga till W3C_Validator i BrowserCaps som vi förklarat tidigare.

<browserCaps>
<case match="W3C_Validator*">
TagWriter = System.Web.UI.HtmlTextWriter
W3CDomVersion = 1.0
</case>
</browserCaps>

Sen även lägga till följande rad så att koden ska följa Strict-standarden. I detta läge får inte form-taggen inte ska få något name-attribut (det får den inte ha i XHTML). Antagligen är det fler saker som händer med koden men vet inte precis.

<xhtmlConformance mode="Strict" />

Den extra diven runt viewstate

I och med .NET 2.0 så har en del förändringar skett så att koden ska bli mer XHTML-kompatibel. Bland annat så läggs numera en extra <div> runt viewstate.

Det här kan dock ställa till problem med CSS formatering. För den nya div-taggen har inte har någon css-klass eller id och är därför svår att styla utan att påverka resten av dokumentet. Den här taggen har flera gånger förstört layouts för mig, så jag förstår de som klagar.

By Jesper Lind

Ladda bara innehåll i flikar på Tabs-kontrollen när det behövs

Tabs-kontrollen i Asp.Net Ajax är cool. Men något att tänka på när man använder den är att alla flikar laddas in när den rendereras, även de som är gömda.

Det fick jag själv bittert erfara när jag hade utvecklat en omfattande applikation med Tabs. Den var seg som sirap när jag väl körde den på produktionsservern.

Om man har mycket funktionallitet under varje flik, vore det bättre om denna bara laddades när man klickar på den. Shawn Burke verkar ha en smidig lösning, den artikeln ska jag kolla på nästa gång det är dags för att använda Tabs.

Uppdatering: Via en av ScottGu's länktips-postningar (massa bra AJAX-releaterat) hittade jag Matt Berseth's blogg som också skriver om hur man kan göra så att flikar i Tabs laddar först när man klickar på dem. Han har också ett demo på "Lazy-load-tabs".

By Jesper Lind