Skicka mail med IIS på din lokala maskin

Har ägnat nån timme åt att försöka skicka mail med SMTP-servern i IIS 5.1 och nu lyckats få iväg några mail. Här går jag igenom de steg jag fick göra för att få det att fungera.

Mailkoden

Själva mailkoden jag använder i applikationen använder System.Net.Mail och ser ut ungefär så här:

SmtpClient smtpClient = new SmtpClient("localhost");
//smtpClient.DeliveryMethod = SmtpDeliveryMethod.PickupDirectoryFromIis;
MailAddress from = new MailAddress("from@dummysite.domain", "Alias from name");
MailAddress to = new MailAddress("to@dummysite.domain");
MailMessage mail = new MailMessage(from, to);
mail.IsBodyHtml = true;

mail.Subject = "From my local computer";
mail.Body = "Testing, testing";

smtpClient.Send(mail);

"SmtpDeliveryMethod.PickupDirectoryFromIis" var något jag trodde behövdes ett tag, eftersom jag läste om det på ett forum, men det visade sig att det gick bra att skicka även utan.

Ställa in SMTP-Server

Det problemet kom i form av ett felmeddelandet som såg ut så här:

Mailbox unavailable. The server response was: 5.7.1 Unable to relay for ...

Lösningen till detta hittade jag på CodeBetter och innebar att lägga till 127.0.0.1 som giltigt IP-nummer för att skicka mail från SMTP-server. (Egenskaper / Åtkomst / Anslutning / Endast datorer i listan nedan)

När detta var klart så försökte jag skicka igen, nu fungerade det i applikationen men mailen fastnade i C:\Inetpub\mailroot\Queue.

Lösningen på detta fann jag på Velocityreviews där medlemmarna förklarade att man behövde lägga till en giltigt SMTP från sin ISP. (Egenskaper / Leverans / Avancerat / Smart värd)

Hoppas detta kan hjälpa någon som vill kunna skicka mail från sin egen dator med ASP.NET.

By Jesper Lind

Posta form-data till en extern URL

Jag kommer ihåg när man jobbade med klassisk ASP, och gick över till ASP.NET, att begreppet PostBack var rätt förvirrande. Typ: "Vadå ska varje sida posta tillbaks till sig själv?" Efter ett tag förstod man finessen och nu skulle skulle jag inte vilja byta tillbaks till hur föregångaren fungerade.

Ibland vill man dock kunna posta data från ett formulär till en annan URL, speciellt när man har att göra med betalväxlar (t ex Postens betalväxel), som ofta fungerar så.

PostBackUrl

I ASP.NET 1.1 var det rätt begränsade möjligheter men i 2.0 fick vi attributet PostBackUrl som kan sättas på Button, LinkButton och ImageButton. Har aldrig använt det själv men borde fungera bra.

HttpWebRequest

Med HttpWebRequest kan man skicka webbströmmar med data. Passar bäst om man vill att postningen ska ske i bakgrunden om jag förstått det rätt, och inte lika bra om man vill att webbläsaren ska följa URLen, som postningen sker till. Jag kan ha fel här för har inte provat så mycket med denna metod. Här är ett exempel som verkar rätt lätt att förstå.

Med JavaScript

Man kan lägga till ett extra formulär på sidan och posta detta genom att registrera ett javascript, från koden bakom, ungefär så här.

string sendScript = "<script language='javascript'>document.getElementById('paymentform').submit();<"+"/script>";
Page.RegisterStartupScript("SendPayment", sendScript);

På det extra formuläret anger man den externa URLen i action och variablerna som hidden-inputs.

<form id="paymentform" name="paymentform" action="https://thepaymentservice.domain" method="post">
<input type="hidden" id="Card_num" runat="server">
<input type="hidden" id="Currency" runat="server">
<input type="hidden" id="Amount" runat="server">
</form>

Eller så bygger man formuläret själv

Här är ett exempel på hur man kan använda HttpContext för att helt enkelt bygga ihop formuläret själv med html-taggar. Det är skrivet av Jigar Desai och finns att ladda hem på C# Corner.

Här kommer lätt modifierad variant:

using System;
using System.Web;
using System.Text;
using System.Collections.Specialized;

/// <summary>
/// Summary description for RemotePost
/// </summary>

public class RemotePost
{
private NameValueCollection inputs = new NameValueCollection();

private string url = string.Empty;
public string Url
{
set{ url = value; }
}
private string method = "post";
private string formName = "form1";

public void Add(string name, string value)
{
inputs.Add(name, value);
}

public void Post()
{
HttpContext.Current.Response.Clear();

StringBuilder sb = new StringBuilder();

sb.Append("<html><head>");

sb.Append(string.Format("</head><body onload=\"document.{0}.submit()\">", formName));
sb.Append(string.Format("<form name=\"{0}\" method=\"{1}\" action=\"{2}\" >", formName, method, url));

for (int i = 0; i < inputs.Keys.Count; i++)
{
sb.Append(string.Format("<input name=\"{0}\" type=\"hidden\" value=\"{1}\">", inputs.Keys[i], inputs[inputs.Keys[i]]));
}

sb.Append("</form>");
sb.Append("</body></html>");

HttpContext.Current.Response.Write(sb.ToString());

HttpContext.Current.Response.End();
}
}

Man anropar klassen så här:

RemotePost remotePost = new RemotePost();
remotePost.Url = "http://theRemoteDomain/Page.aspx";
remotePost.Add("field1","This is my first value");
remotePost.Add("field2","And another one");
remotePost.Post();

By Jesper Lind

Variera OutputCache beroende på sökvägens mapp

Jag har undrat ett tag över hur man kan basera OutputCache beroende på vilken mapp en sida ligger i, och nu kom jag på en lösning.

I mitt fall var det några User Controls som jag ville ha separata Cache-versioner av i varje mapp i applikationen. Först lade jag till följade deklaration på dessa:

<%@ OutputCache Duration="1800" VaryByParam="none"  VaryByCustom="CustomFolder"  %>

Sen overridade jag GetVaryByCustomString i Global.asax som man ska göra om man vill använda VaryByCustom.

public override string GetVaryByCustomString(
HttpContext context,
string arg)
    {
        string result = String.Empty;

        if (arg == "CustomFolder")
        {
            //object o = HttpContext.Current.Request.Path;<--This also gets the file name
           
            //Get the current folder, without the file name
            object o = HttpContext.Current.Request.Path.Substring(0,HttpContext.Current.Request.Path.LastIndexOf("/"));
            if (o != null)
            {
                result = o.ToString();
            }
        }
        else
        {
            result = base.GetVaryByCustomString(context, arg);
        }

        return result;
    }

Raden där den aktuella mappen hämtas ut blev lite lång och krånglig. Ville alltså ha bara mappen och inte filnamnet. Läste här att man kan använda Page.TemplateSourceDirectory, men kunde inte komma åt en referens till Page från Global.asax, eller hitta en motsvarande funktion i HttpContext.

By Jesper Lind

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

Nullable types

Något jag inte tänkt på innan är att man inte kan sätta värde-variabler till null i C#. Gjorde ett försök och fick då felet:

int x = null;
Cannot convert null to 'int' because it is a value type

När jag läst detta inlägget, fick jag lite bättre förståelse till hur det fungerar, och även hur man kan göra för att använda null även på värdetyper, så här:

int? x = null;

Användbart om man jobbar med en databas och man vill att sina variabler ska reflektera de null-värden som finns där.

Även CodeBetter skriver mer om saken. 

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

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

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

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