Lang, lang ist’s her, dass hier was passiert ist, aber es geschehen doch noch Wunder. Zur Zeit hab ich immer mehr mit WPF/XAML zu tun, aber auch mit normaler Web-Entwicklung (keine RIAs). Wie passen diese beiden Dinge nun zusammen? Richtig, eigentlich gar nicht. Da meine Begeisterung was WPF/XAML angeht aber täglich wächst und ich diese Möglichkeiten auch in einer Standard-ASP.NET-Anwendung ohne Silverlight nutzen möchte, habe ich in den letzten paar Stunden eine recht nette Möglichkeit dafür entwickelt.
Grob gesehen ging es mir nur darum, mit WPF erzeugte grafische Effekte irgendwie darstellen zu können – am besten als einfaches Bild (JPEG, PNG, etc.). Gesagt, getan, entstanden ist ein HttpHandler, der die Aufgabe hat, eine Eingangs-XAML-Datei in eine Ausgangs-PNG-Datei umzuwandeln.
Für die Entwicklung hatte ich mir vorgenommen, den Header von http://visitmix.com/2008 in leicht modifizierter Form in XAML nach zu bauen. Ausgerüstet mit dem XAMLPad ging es also an die Header-Grafik inklusive MIX08-Logo. Herausgekommen ist das:
images\header.xaml
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="1000" Height="140">
<Page.Resources>
<PathGeometry x:Key="rect" Figures="M 0,0 L 15,0 L 15,15 L 0,15" />
<PathGeometry x:Key="rect_1r" Figures="M 15,0 L 15,15 L 0,15 L 0,7.5 Q 0,0 7.5,0" />
<PathGeometry x:Key="rect_2r" Figures="M 15,0 L 15,7.5 Q 15,15 7.5,15 L 0,15 L 0,8 Q 0,0 8,0" />
</Page.Resources>
<Grid>
<Path Stroke="#333" StrokeThickness="2" Data="M -5,139 L 200,139 Q 220,139 250,100 Q 280,60 300,60
L 800,60 Q 820,60 850,100 Q 880,139 900,139 L 1005,139
L 1005,-5 L -5,-5">
<Path.Fill>
<LinearGradientBrush StartPoint="0,1">
<GradientStop Offset="0" Color="#FFF" />
<GradientStop Offset="1" Color="#EEE" />
</LinearGradientBrush>
</Path.Fill>
</Path>
<Canvas>
<Canvas Canvas.Top="20" Canvas.Left="20">
<Canvas.RenderTransform>
<ScaleTransform ScaleX="1.4"
ScaleY="{Binding RelativeSource={RelativeSource self}, Path=ScaleX}" />
</Canvas.RenderTransform>
<Canvas.BitmapEffect>
<OuterGlowBitmapEffect GlowColor="Black" />
</Canvas.BitmapEffect>
<Canvas.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="#F93366" />
</Style>
</Canvas.Resources>
<Path Canvas.Top="47" Canvas.Left="0" Data="{StaticResource rect_1r}">
<Path.RenderTransform>
<RotateTransform Angle="270" />
</Path.RenderTransform>
</Path>
<Path Canvas.Top="16" Canvas.Left="0" Data="{StaticResource rect}" />
<Path Canvas.Top="0" Canvas.Left="0" Data="{StaticResource rect_1r}" />
<Path Canvas.Top="0" Canvas.Left="31" Data="{StaticResource rect_2r}">
<Path.RenderTransform>
<RotateTransform Angle="90" />
</Path.RenderTransform>
</Path>
<Path Canvas.Top="0" Canvas.Left="32" Data="{StaticResource rect_2r}">
<Path.RenderTransform>
<RotateTransform Angle="0" />
</Path.RenderTransform>
</Path>
<Path Canvas.Top="0" Canvas.Left="63" Data="{StaticResource rect_1r}">
<Path.RenderTransform>
<RotateTransform Angle="90" />
</Path.RenderTransform>
</Path>
<Path Canvas.Top="16" Canvas.Left="48" Data="{StaticResource rect}" />
<Path Canvas.Top="47" Canvas.Left="63" Data="{StaticResource rect_1r}">
<Path.RenderTransform>
<RotateTransform Angle="180" />
</Path.RenderTransform>
</Path>
<Path Canvas.Top="15" Canvas.Left="69" Data="M 15,0 L 15,7.5 Q 15,15 7.5,15 Q 3.25,15 1.25,10.25
Q 0,0 8,0">
<Path.RenderTransform>
<RotateTransform Angle="270" />
</Path.RenderTransform>
</Path>
<Path Canvas.Top="16" Canvas.Left="69" Data="{StaticResource rect}" />
<Path Canvas.Top="47" Canvas.Left="84" Data="{StaticResource rect_1r}">
<Path.RenderTransform>
<RotateTransform Angle="180" />
</Path.RenderTransform>
</Path>
<Path Canvas.Top="0" Canvas.Left="104" Data="{StaticResource rect_2r}">
<Path.RenderTransform>
<RotateTransform Angle="90" />
</Path.RenderTransform>
</Path>
<Path Canvas.Top="16" Canvas.Left="104" Data="{StaticResource rect}" />
<Path Canvas.Top="32" Canvas.Left="135" Data="{StaticResource rect_2r}">
<Path.RenderTransform>
<RotateTransform Angle="90" />
</Path.RenderTransform>
</Path>
<Path Canvas.Top="47" Canvas.Left="104" Data="{StaticResource rect_2r}">
<Path.RenderTransform>
<RotateTransform Angle="180" />
</Path.RenderTransform>
</Path>
<Path Canvas.Top="15" Canvas.Left="135" Data="{StaticResource rect_2r}">
<Path.RenderTransform>
<RotateTransform Angle="180" />
</Path.RenderTransform>
</Path>
</Canvas>
<Path Stroke="Black" StrokeThickness="2" Canvas.Top="47" Canvas.Left="193"
Data="M 0,0 L 10,0 L 10,11 L 0,11 L 0,0" />
<Path Stroke="Black" StrokeThickness="2" Canvas.Top="47" Canvas.Left="207"
Data="M 10,5 L 0,5 L 0,0 L 10,0 L 10,11 L 0,11 L 0,0" />
<TextBlock Canvas.Top="95" Canvas.Left="20" FontFamily="Segeo UI" FontSize="22" Foreground="#666"
Text="The Next Web Now" />
</Canvas>
</Grid>
</Page>
Um nun in der ASP.NET-Anwendung auf XAML Funktionalitäten zugreifen zu können, müssen in der web.config noch 3 Assembly-Referenzen hinzugefügt werden:
web.config
<system.web>
<compilation debug="false">
<assemblies>
...
<add assembly="WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add assembly="PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add assembly="PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</assemblies>
</compilation>
...
Nun geht es an den eigentlichen Kern dieser Beispielanwendung – den HttpHandler. Ein Httphandler wird auf bestimmt Dateitypen angewendet. Da ich es für nicht sinnvoll halte, ihn komplett auf alle *.xaml Dateien anzusetzen, hab ich mich für die Erweiterung .xpng entschieden. Diese wird im Handler dann für den Zugriff auf die XAML-Datei umgeschrieben:
App_Code\XamlImageHandler.cs
using System;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Web;
public class XamlImageHandler : IHttpHandler
{
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "image/png";
Thread t = new Thread(new ParameterizedThreadStart(InternalProcessRequest));
t.SetApartmentState(ApartmentState.STA);
t.Start(context);
t.Join();
}
#endregion
private void InternalProcessRequest(object context)
{
HttpContext ctx = context as HttpContext;
string xamlFilename = Path.ChangeExtension(
ctx.Server.MapPath(ctx.Request.AppRelativeCurrentExecutionFilePath), ".xaml");
System.Windows.Controls.Page page = (System.Windows.Controls.Page)XamlReader.Load(
new FileStream(xamlFilename, FileMode.Open));
int width = (int)page.Width;
int height = (int)page.Height;
page.Arrange(new Rect(new Point(0, 0), new Point(width, height)));
page.UpdateLayout();
RenderTargetBitmap bmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Default);
bmp.Render(page);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(bmp));
using (MemoryStream stream = new MemoryStream())
{
png.Save(stream);
stream.WriteTo(ctx.Response.OutputStream);
}
}
}
…und nicht vergessen, den Handler in der web.config zu registrieren:
web.config
<httpHandlers>
...
<add verb="GET,HEAD" path="*.xpng" type="XamlImageHandler" validate="false"/>
</httpHandlers>
Und das war’s auch schon. Für’s Copy/Paste noch meine statische HTML-Seite und das dazu gehörige CSS:
css\2008.css
body { background-color: #666; font-family: 'Segoe UI', Tahoma, Helvetica, sans-serif;
font-size: 0.8em; margin: 0; padding: 0; }
a { color: #39C; text-decoration: none; }
#container { background: #FFF url(http://visitmix.com/2008/images/container_bg.jpg) no-repeat scroll 0 0;
margin: 0 auto; width: 1000px; height: 500px; }
#header { background-image: url('../images/header.xpng'); height: 140px;
position: fixed; top: 0; width: 1000px; }
#topNav { float: right; font-size: 10px; margin: 0 10px; padding-bottom: 10px;
text-align: right; text-transform: uppercase; }
#topNav ul { list-style-type: none; margin: 10px 0 0; }
#topNav li { border-right: 1px solid #999; height: 11px; margin: 0 6px 1px 0;
padding-right: 6px; width: 200px; }
#topNav li a { color: #333; text-transform: uppercase; }
Default.htm
<!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>
<title>MIX08 Conference - March 5-7, 2008</title>
<link href="css/2008.css" type="text/css" rel="stylesheet" />
</head>
<body>
<div id="container">
<div id="header">
<div id="topNav">
<ul>
<li><a href="http://visitmix.com/2008/default.aspx">Home</a></li>
<li><a href="http://www.visitmix.com/2008/worldwide">Worldwide</a></li>
<li><a href="#">About the Conference</a></li>
<li><a href="#">the Agenda</a></li>
<li><a href="#">Speaker Bios</a></li>
<li><a href="#">Mixtify</a></li>
<li><a href="http://content.visitmix.com/public/sessions.aspx">Sessions</a></li>
<li><a href="#">Sponsors</a></li>
<li><a href="http://visitmix.com">MIX Online</a></li>
</ul>
</div>
</div>
</div>
</body>
</html>
Screenshot der Beispielanwendung