HowTo: Globalization/Localization mit ASP.NET MVC – streng typisiert

imageIn einen meiner letzten HowTos ging es um die Mehrsprachigkeit in ASP.NET MVC Anwendungen. Dort hatte ich ein System empfohlen, welche über bestimmte Expressions die Resourcendatein auslesen. Es geht natürlich viel einfacher und eleganter: Einfach über Resources zugreifen.

 

Weiterlesen »

HowTo: jCrop & ASP.NET MVC – On the fly Bildverkleinern

image Auf vielen Seiten kann man Profilbilder hinterlegen. Meinstens müssen diese eine bestimmte Größe haben, ansonsten wird das Bild gestaucht oder gezerrt. Beides eher suboptimal. Mit jCrop gibt es ein kleines, nützliches jQuery Plugin, welches man benutzen kann um bestimmte Ausschnitte aus einem Bild auszuschneiden. jCrop macht dies Client-Seitig und ich möchte das “ausgeschnittene” Bild nun auch in ASP.NET MVC verarbeiten…

Weiterlesen »

HowToCode: Reverse AJAX / Http-Push / "Comet" – Kann der Server Clients aktiv infomieren?

imageDurch eine Problemstellung bei Kollegen bin ich auf ein interessantes Thema gestoßen: HTTP Push/Reverse Ajax. Bei einem normalen Einsatz von AJAX schickt der Client (Browser) einen Request zum Server und dieser Antwortet – asynchron natürlich. Doch gibt es eine Möglichkeit dass auch der Server den Client informiert – ohne Polling? Hat jemand bereits in diesem Gebiet (mit .NET “Integration” ) Erfahrung gesammelt?

Weiterlesen »

HowTo: Elmah in ASP.NET MVC (& IIS7) fürs Errorlogging

imageIch habe bereits mehrfach über Errorlogging (und Logging im Allgemeinen) geschrieben, jedoch habe ich heute in einem ASP.NET MVC Projekt eine nette Open Source Library eingebaut: ELMAHError Logging Modules and Handlers for ASP.NET.

Weiterlesen »

ASP.NET MVC 1.0 released! (1. Update)

image

Das ASP.NET MVC Framework in der Version 1.0 scheint auf Microsoft Download Seiten aufgetaucht zu sein. Sicherlich wird Phil Haack oder Scott Guthrie demnächst einen entsprechenden Blogeintrag verfassen.

[1. Update]

HowTo: Health Monitoring mit ASP.NET

imageDie Arbeit ist getan, die Website steht! Doch damit fangen die nächsten Probleme an. Kunden rufen an und sagen einem, dass sie eine Error-Page gesehen haben – also mal auf den Server schauen was da eigentlich los ist.
Mit diversen Logging Tools kann man sich über bestimmte Ereignisse informieren lassen – seit ASP.NET 2.0 gibt es auch einen eingebautes Health-Monitoring, dass man sehr einfach nutzen kann.

Weiterlesen »

HowTo: Einstieg in Windows Azure CTP und erstes HelloWorld

imageIn einem letzten Blogpost habe ich über Cloud Computing im Allgemeinen geschrieben.

Darin hab ich bereits etwas über die Windows Azure Plattform gesprochen und möchte nun eine "Hello World" Anwendung auf Windows Azure erstellen.

Weiterlesen »

HowTo: ASP.NET Chart Controls mit ASP.NET MVC nutzen

Microsoft hat heute ein neues kleines ASP.NET Feature veröffentlicht. Die Rede ist von den ASP.NET Chart Controls (welche zum Teil auf den Dundas Charts basieren).
Diese Controls schauen eigentlich ziemlich nett aus:

image

Das beste daran: Es funktioniert mit 2 Tricks auch recht gut mit ASP.NET MVC.

Download Links für die ASP.NET Charts (alles kostenlos) :

Web.Config editieren

Unter "<system.web><pages><controls>" dieses hinzufügen:

<add tagPrefix="asp" namespace="System.Web.UI.DataVisualization.Charting" assembly="System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

Und unter den "<httpHandlers>" folgendes:

<add path="ChartImg.axd" verb="GET,HEAD" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>

Meine Quelle: Combining ASP.NET MVC and ASP.NET Charting Controls

Chart Control hinzufügen:

Variante A: ASP.NET Control + Code behind

Bei dieser Variante muss man ein paar Zeilen Code in der Code behind hinzufügen, allerdings sollte die gesamten Daten vom Controller kommen, sodass man dies noch als OK ansehen kann.

Ich habe dabei das "GettingStartet" Control aus den Samples genommen.

Index.aspx:

<asp:chart id="Chart1" runat="server" Height="296px" Width="412px" Palette="BrightPastel" imagetype="Png" BorderDashStyle="Solid" BackSecondaryColor="White" BackGradientStyle="TopBottom" BorderWidth="2" backcolor="#D3DFF0" BorderColor="26, 59, 105">
	<Titles>
		<asp:Title Text="With datasource in code behind" />
	</Titles>
	<legends>
		<asp:Legend IsTextAutoFit="False" Name="Default" BackColor="Transparent" Font="Trebuchet MS, 8.25pt, style=Bold"></asp:Legend>
	</legends>
	<borderskin skinstyle="Emboss"></borderskin>
	<series>
		<asp:Series Name="Column" BorderColor="180, 26, 59, 105">
		</asp:Series>
	</series>
	<chartareas>
		<asp:ChartArea Name="ChartArea1" BorderColor="64, 64, 64, 64" BorderDashStyle="Solid" BackSecondaryColor="White" BackColor="64, 165, 191, 228" ShadowColor="Transparent" BackGradientStyle="TopBottom">
			<area3dstyle Rotation="10" perspective="10" Inclination="15" IsRightAngleAxes="False" wallwidth="0" IsClustered="False"></area3dstyle>
			<axisy linecolor="64, 64, 64, 64">
				<labelstyle font="Trebuchet MS, 8.25pt, style=Bold" />
				<majorgrid linecolor="64, 64, 64, 64" />
			</axisy>
			<axisx linecolor="64, 64, 64, 64">
				<labelstyle font="Trebuchet MS, 8.25pt, style=Bold" />
				<majorgrid linecolor="64, 64, 64, 64" />
			</axisx>
		</asp:ChartArea>
	</chartareas>
</asp:chart>

Was das genau bedeuted kann in der Doku nachgelesen werden, wichtig ist, dass wir eine "Serie" namens "Column" haben – dies repräsentiert später unseren Balken auf dem Balkendiagram.

Index.aspx.cs:

    public partial class Index : ViewPage
    {
        protected void Page_Load(object sender, System.EventArgs e)
        {
            foreach (int value in (List<int>)this.ViewData["Chart"])
            {
                this.Chart1.Series["Column"].Points.Add(value);
            }
        }
    }

Ergebnis:

image

Variante B: Inline ASP.NET Control

Ohne Codebehind, direkt auf der Seite mit Inline Code:

        <p>
        <%
						System.Web.UI.DataVisualization.Charting.Chart Chart2 = new System.Web.UI.DataVisualization.Charting.Chart();
                        Chart2.Width = 412;
                        Chart2.Height = 296;
                        Chart2.RenderType = RenderType.ImageTag;

                        Chart2.Palette = ChartColorPalette.BrightPastel;
                        Title t = new Title("No Code Behind Page", Docking.Top, new System.Drawing.Font("Trebuchet MS", 14, System.Drawing.FontStyle.Bold), System.Drawing.Color.FromArgb(26, 59, 105));
                        Chart2.Titles.Add(t);
                        Chart2.ChartAreas.Add("Series 1");

						// create a couple of series
                        Chart2.Series.Add("Series 1");
                        Chart2.Series.Add("Series 2");

						// add points to series 1
                        foreach (int value in (List<int>)ViewData["Chart"])
                        {
                            Chart2.Series["Series 1"].Points.AddY(value);
                        }

                        // add points to series 2
                        foreach (int value in (List<int>)ViewData["Chart"])
                        {
                            Chart2.Series["Series 2"].Points.AddY(value + 1);
                        }

                        Chart2.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
                        Chart2.BorderColor = System.Drawing.Color.FromArgb(26, 59, 105);
                        Chart2.BorderlineDashStyle = ChartDashStyle.Solid;
                        Chart2.BorderWidth = 2;

                        Chart2.Legends.Add("Legend1");

						// Render chart control
                        Chart2.Page = this;
						HtmlTextWriter writer = new HtmlTextWriter(Page.Response.Output);
						Chart2.RenderControl(writer);

                     %>
        </p>

Ergebnis:

image

Beide Controls zusammen:

image

[ Download Source Code ]

HowTo: Dynamisch ASP.NET Controls mit AJAX erzeugen

Die Idee (und einige Codezeilen) für dieses HowTo kommt von diesem Blogeintrag der ein tolles Beispiel zeigt. Da ich mir meistens Sachen besser merken kann, wenn ich diese selber mal prototypisch umsetze, schreibe ich auf dieser Basis das HowTo.

Worum geht es?
Stellen wir uns vor, wir hätten viele UserControls geschrieben, welche Daten und Controls unterschiedlicher Art anzeigen können.
Wenn man nun allerdings eine schöne “Web 2.0″ AJAX Anwendung bauen will, schickt man im einfachsten Fall JSON Daten hin und her und muss sich das Control über Javascript zusammenbauen. Das ganze ist natürlich nicht sehr wartbar (manches wird über ASP.NET gemacht, manches vielleicht über statisch eingebundene Controls und andere Sachen über Javascript) – schön wäre doch, wenn man die Controls dynamisch auf die Seite holen könnte. Und genau darum geht es heute…

Aufbau:

image

Wir haben ein sehr simples “SampleUserControl”:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="SampleUserControl.ascx.cs" Inherits="DynamicASPControls.SampleUserControl" %>
<div style="background-color: red; height: 200px; top: 200px">
    Fertig geladen \o/
</div>

Der ScriptService:

Für die Kommunikation zwischen Client und Server (und die AJAX Funktionalität) nutzen wir ASP.NET AJAX. Wie das geht und was man damit machen kann, habe ich hier bereits beschrieben.

Im ScriptService (der auch das Attribut ScriptService trägt) erstellen wir uns dynamisch den HTML Code von einem beliebigen Usercontrol:

public class ScriptService : System.Web.Services.WebService
    {
        [WebMethod(EnableSession = true)]
        public string GetControlHtml(string controlLocation)
        {
            Page page = new Page();
            UserControl userControl = (UserControl)page.LoadControl(controlLocation);
            userControl.EnableViewState = false;
            HtmlForm form = new HtmlForm();
            form.Controls.Add(userControl);
            page.Controls.Add(form);
            StringWriter textWriter = new StringWriter();
            HttpContext.Current.Server.Execute(page, textWriter, false);
            return CleanHtml(textWriter.ToString());
        }

        /// <summary>
        /// Removes Form tags
        /// </summary>
        private string CleanHtml(string html)
        {
            return Regex.Replace(html, @"<[/]?(form)[^>]*?>", "", RegexOptions.IgnoreCase);
        }
    }

Der “GetControlHtml” Methode wird die “Location” von dem Control mitgeiteilt und jetzt bauen wir uns dynamisch eine “Page” zusammen und fügen eine “HtmlForm” dazu und hängen dort das geladene Control dran.

Über “HttpContext.Current.Server.Execute” können wir nun unseren fertigen HTML Code in ein String verwandeln.

Die “CleanHtml” Methode entfernt die Form Tags am Anfang wieder – sodass wir möglichst nur noch das original Control-HTML übrig haben.

Größenbeschränkung aufheben:

Da man ja relativ viele Sachen in einem Control machen kann und das daraus resultierende HTML recht groß werden kann, müssen wir dies in der Web.Config erst freischalten:

  <system.web.extensions>
    <scripting>
      <webServices>
        <jsonSerialization maxJsonLength="5000000" />
      </webServices>
    </scripting>
  </system.web.extensions>

Fertige ASPX:

Die fertige ASPX Seite ist bewusst einfach gehalten:

<%@ Page Language="C#" AutoEventWireup="true" EnableViewState="false" CodeBehind="Default.aspx.cs" Inherits="DynamicASPControls._Default" %>

<!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>Untitled Page</title>

</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Services>
            <asp:ServiceReference Path="~/ScriptService.asmx" />
        </Services>
    </asp:ScriptManager>

    <button type="button" onclick="generateUserControl()">Generate UserControl!</button>
    <div id="result">

    </div>
    </form>

        <script type="text/javascript">
        function generateUserControl()
        {
            document.getElementById("result").innerHTML = "Load...";
            DynamicASPControls.ScriptService.GetControlHtml("~/SampleUserControl.ascx", generateUserControlCompleted);
        }

        function ready(result)
        {
            alert(result);
        }

        function generateUserControlCompleted(result)
        {
            document.getElementById("result").innerHTML = result;
        }

        function failed(error)
        {
            alert(error);
        }

    </script>
</body>
</html>

Über die JavaScript Methoden greifen wir auf den Service zu, welcher uns das HTML liefert. In der “generateUserControlCompleted” Methode schreiben wir das HTML nur noch in unser “result” div:

image
(Design made by me ;) )

Ein komplexeres Beispiel:

Ein wesentlich komplexeres Beispiel findet ihr in diesem Blogpost, sowie eine jQuery Variante in diesem Blogpost.

Wo es Probleme geben könnte:

Da wir das HTML dynamisch zur Seite hinzufügen, funktioniert sicherlich das Postback System von ASP.NET nicht (bzw. könnte ich es mir vorstellen, dass es da Probleme gibt) – für ein einfaches Control, welches ohne PostBacks entwickelt wurde, ist es aber sicherlich eine tolle Sache.

[ Download Sourcecode ]

Dateien per Namespace aufrufen

Ich hatte ver kurzem das Problem das ich auf eine XML Datei in meinem Projekt zugreifen musste aber den genauen Pfad nicht kannte. Das erzeugen eines dynamischen Pfades per “AppDomain.CurrentDomain.BaseDirectory” war nicht möglich da ich mit Usercontrols arbeitete und so nur den Pfad der Datei bekam die mein Uercontrol einbindet.
Gelöst habe ich das Problem in dem ich über den Namespace auf die XML Datei zugegriffen habe:

// XmlResource  = Namespace + Dateiname
string XmlResource = "CodeInside.WebApp." + "Daten.xml";

  using (Stream FileStream = System.Reflection.Assembly.
    GetExecutingAssembly().GetManifestResourceStream(XmlResource))
  {
    XmlSerializer Ser = new XmlSerializer(typeof(List));
    StreamReader Sr = new StreamReader(FileStream );
    List Data = (List)Ser.Deserialize(Sr);
    Sr.Close();
  }

Damit man auf die Datei auch zugreifen kann muss man in den Eigeschaften der Datei das Feld “Build Action” auf “Embedded Resource” stellen.
Embedded Resource

ASP.NET MVC – "AddIns" auf Codeplex

Da ich gerade selber an einem MVC "Testprojekt" arbeite, schau ich mir natürlich auch mal andere Ansätze oder "Zusätze" an, welche sicherlich ein Blick wert sind. Achtet allerdings auf die verwendete Lizenz.

Troy Goode´s Implementation von dem ASP.NET Membership System in MVC – schade das ich meine MVC Membership Version nicht besser bekannt gemacht hab (naja, nochmal passiert das nicht ;) )

>> Lizenz

Jedes Eingabefeld benötigt irgendwo eine Validierung – hier ein interessanter Ansatz wie man die geschickt mit Filtern und jQuery lösen könnte.

>> Lizenz

4 neue Viewengines, Code Snippets, Extensions etc.

>> Lizenz

 

Das MVC Demoprojekt überhaupt: Kigg (Lizenz)

HowTo: Excel-Export via AJAX

Wir (d.h. Oliver und ich) haben bei einem Projekt die Anforderung gehabt, in einer sehr AJAX und Javascriptlastigen Anwendung ein Excel Export mit einzubauen.

Durch ein Postback oder durch Aufruf einer Webmethode etc. ist dies sehr einfach zu lösen, allerdings gab es ein Problem:

Die letztendliche Ergebnisliste wird über verschiedene Javascript Methoden befüllt, d.h. das momentan angezeigte (und in einem JSON Objekt befindliche) Ergebnis muss am besten direkt irgendwie übergeben werden.

Da die Lösung etwas "geschickt" ist (der Erfinder ist Oliver) Schritt für Schritt erklärt:

1. Grundsätzlicher Aufbau

image

2. Default.aspx vorbereiten

Die ASPX Seite (abgesehen von dem Javascript – welches gleich näher beschrieben wird) ist simpel:

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!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>Untitled Page</title>
    <script type="text/javascript">
    // wird gleich gezeigt
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <button type="button" onclick="createXlsData()">Create XLS</button>
    </div>
    </form>
</body>
</html>

Zwischenbemerkung: Wie erstellt man eigentlich eine Excel List?

Excel kann auch HTML Tabellen "interpretieren" und entsprechend darstellen.

Das HTML drumherum muss man weg lassen, um eine einfache Tabelle zu erzeugen muss man Excel sowas übergeben:

<table>
  <tr>
     <td>Sample</td>
  </tr>
</table>

3. Im Javascript Tabelle vorbereiten: "createXlsData()"

In diesem Teil können wir nun auf unsere JSON Objekte zugreifen – ohne nochmal einen Webservice etc. zu befragen.

function createXlsData()
    {
    var data = "<table>";
            data += "<tr>";
                data += "<td>Test 1</td>";
                data += "<td>Test 2</td>";
             data += "</tr>"

    for(i=0; i < 10; i++)
    {
        var singleLine = "<tr>";
                singleLine += "<td> Test Data1: " + i + "</td>";
                singleLine += "<td> Test Data2: "  + i + "</td>";
        singleLine += "</tr>";
        data += singleLine;
    }
        data += "</table>";
    sendXlsData(data);
    }

4. Daten an Handler übermitteln und Handler aufrufen

Da der String für einen "GET" Request zu lang ist, muss er per "POST" an den Handler übermittelt werden . natürlich mit AJAX.

Problem: Wenn man dies per AJAX macht, sieht man nie dieses Fenster:

image

Lösung: Der Handler speichert die übergebenen Werte in der Session und das Javascript ruft expliziert nochmal den Handler aus, welcher die Daten aus der Session holt.

Hier der komplette Source-Code der Default.aspx:

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!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>Untitled Page</title>
    <script type="text/javascript">
    var DataExchangeHttpRequest;

    function createXlsData()
    {
    var data = "<table>";
            data += "<tr>";
                data += "<td>Test 1</td>";
                data += "<td>Test 2</td>";
             data += "</tr>"

    for(i=0; i < 10; i++)
    {
        var singleLine = "<tr>";
                singleLine += "<td> Test Data1: " + i + "</td>";
                singleLine += "<td> Test Data2: "  + i + "</td>";
        singleLine += "</tr>";
        data += singleLine;
    }
        data += "</table>";
    sendXlsData(data);
    }

    function sendXlsData(data)
    {
    if (window.XMLHttpRequest) // Mozilla, Safari, Opera, IE7
        {
        DataExchangeHttpRequest = new XMLHttpRequest();
        }
    else if (window.ActiveXObject) // IE6, IE5
        {
        DataExchangeHttpRequest = new ActiveXObject("Microsoft.XMLHTTP");
        }

    DataExchangeHttpRequest.onreadystatechange = openXlsPage;
    DataExchangeHttpRequest.open('POST', 'ExcelHandler.ashx', true);
    DataExchangeHttpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
    DataExchangeHttpRequest.setRequestHeader('Content-Length',data.length);

    DataExchangeHttpRequest.send(data);
    }
    function openXlsPage()
    {
        if (DataExchangeHttpRequest.readyState == 4 && DataExchangeHttpRequest.status == 200)
        {
        window.open("ExcelHandler.ashx?openXls=true","xls");
        }
    }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <button type="button" onclick="createXlsData()">Create XLS</button>
    </div>
    </form>
</body>
</html>

5. Der ASHX Handler

<%@ WebHandler Language="C#" Class="ExcelHandler" %>

using System;
using System.Web;

public class ExcelHandler : IHttpHandler, System.Web.SessionState.IRequiresSessionState
{

    public void ProcessRequest(HttpContext context)
    {

        if (context.Request.Params["openXls"] == "true")
        {
            // display data
            if (context.Session["ExcelData"] != null)
            {
                context.Response.ContentType = "application/vnd.ms-excel";
                context.Response.ContentEncoding = System.Text.Encoding.Default;
                context.Response.Write(context.Session["ExcelData"]);
            }
            else
            {
                context.Response.Write("session empty");
            }
        }
        else
        {
            // save data in session
            context.Response.ContentType = "application/vnd.ms-excel";
            string data = "";
            using (System.IO.StreamReader reader = new System.IO.StreamReader(context.Request.InputStream))
            {
                data = reader.ReadToEnd();
            }

            context.Session.Add("ExcelData", data);
        }
    }
    public bool IsReusable {
        get {
            return true;
        }
    }

}

Durch den Content Type "application/vnd.ms-excel" wird diese Javascript-generierte Tabelle von Excel erkannt.

Schematische Darstellung

image

Als Nutzer sieht das dann so aus

Klicken:

image

Fenster öffnet sich:

image

Im Excel sieht das dann so aus:

image

Bei der momentanen Lösung kommt da allerdings am Anfang noch eine Warnung, dass eine ASHX versucht was an Excel zu schicken – das bekommt man sicherlich auch noch irgendwie raus.

Wo macht das Sinn?

Das ganze macht da Sinn, wo man mit viel Javascript und AJAX sich Daten z.B. in einem JSON Objekt gesammelt hat und diese nun auch an Excel exportieren möchte – was bei uns der Fall war. Wenn man natürlich die Daten aus einer DB holen kann, ist sowas natürlich über ein Postback oder wie auch immer "sauberer".

Das ganze gehört eindeutig in die Kategorie: Freakige Sachen ;)

[ Download Source ]

HowTo: ASP.NET Seiten komprimieren per global.asax

Wenn man größere Datenmegen auf einer ASP Seite anzeigen oder per Webservice übertragen möchte, kann man die Übertragungsdauer durch Komprimierung erheblich reduzieren (je nach Inhalt schrumpft die Datenmenge auf ca. 1/4).
Eine ausführliche Anleitung wie man die Komprimierung (gzip und deflate) ohne großen Aufwand und Änderungen am Code einrichtet findet ihr hier.

Hier die Kurzanleitung:

1. Füge diese Datei in dein Projekt ein.
2. Webseite neu bauen.
3. Fertig :)

HowTo: ASP.NET Membership, Roles & Profiles – Einrichten, Rollensystem, Allgemeines & Demoprojekt

Jetzt werde ich sicherlich über ein etwas älteres Thema schreiben. Allerdings hatte ich erst jetzt damit zutun und da ich doch einige Anfangsschwierigkeiten hatte, werde ich hier nach und nach Informationen zum Membership System von ASP.NET niederschreiben. Ziel soll es sein auch CardSpace oder OpenID mit einzubinden – mal sehn wann dies geschehen wird.

Dies ist der “Einstiegsteil” in das Thema, fangen wir daher nun an:

Membership? Was ist das?

Das ASP.NET Membership System wurde mit ASP.NET 2.0 eingeführt und stellt eine einheitliche API zum Benutzer-, Rollen-, Profilmanagement sowie Authentifierung bereit. Zudem gibt es noch einige UI Controls in ASP.NET.

Das ganze Membership System ist zudem erweiterbar – dazu gibt es ein Providermodell.

Wie funktioniert das ganze?

Von Microsoft direkt gibt es ein Provider für Active Directory und für den SQL Server (desweiteren kann man noch in der web.config feste Nutzer einrichten). Der SQL Provider ist für Internetanwendungen geeignet, dabei wird von ASP.NET eine solche DB erzeugt:

image

Es gibt auch bereits Provider für alles mögliche – z.B. auch für MySQL. Natürlich kann man auch seinen eigenen Membership Provider schreiben. Schauen wir uns erstmal Schritt für Schritt an, was man tun muss, um eine solche Datenbank zu bekommen.

Schritt 1: Web Site anlegen & ASP.NET Configuration starten

Ich empfehle eine “Web Site”, weil es in Zusammenhang mit dem Profil System und “Web Projects” zu Problemen kommen kann. Visual Studio sollte zudem möglichst unter dem Admin Konto ausgeführt werden – bei mir hat es ansonsten immer mal geklemmt.

Sobald man solch ein Web Site angelegt hat, gibt es im Menü den Punkt “Website”, dort einfach auf “ASP.NET Configuration” gehen:

image

Danach öffnet sich eine Website mit mehreren Reitern – der interessanteste Teil dürfte sicherlich “Sicherheit” sein:

image

Unter “Authentifierzungstyp auswählen” kann man zwischen den SQL Provider und den AD Provider wechseln. Wenn man das erste mal den Punkt “Aus dem Internet” (also den SQL Provider) gewählt hat, dann wird die obrige Datenbank erstellt.
Hinweis: Das aspnet_regsql Tool erlaubt dies auch auf Basis der Kommandozeile.

Recht komfortabel kann man hier zudem Nutzer und Rollen einrichten und Zugriffsregeln setzen. Die Zugriffsregeln werden dabei jeweils in der web.config abgespeichert.

Wichtig ist, dass die Nutzer nach dem Anlegen aktiviert sind:

image

Rollen lassen sich ebenso managen.

Hinweis: Der Reiter “Anbieter” wird bei einem Custom Membership Provider interessant.

Schritt 2: ASP.NET Login Controls verwenden

Man kann den gesamten Authentifierungsprozess & das restliche Management über die Membership Klassen abbilden, allerdings hat Microsoft eine handvoll nützliche Controls bereits definiert:

image

Wenn man nichts verstellt hat, kann man das Login Control auf eine ASPX Seite ziehen und sich (nachdem Nutzer eingerichtet wurden) auch gleich anmelden. Einen Überblick über die einzelnen Controls gibt es hier.

Die meisten Controls können angepasst werden, z.B. das Login Control, aber auch andere.

Schritt 3: Grundgerüst erstellen

Ich mache mal eine Beispielhafte Implementation, welche ich am Ende auch zum Downloaden anbiete.

Das Grundgerüst sieht so aus

image

Es wurden 3 Nutzer eingerichtet und 3 Rollen – Benutzer, Redakteur und Administrator.

In dem Ordner “Redakteur” kommen nur Redakteure – in den Ordner “Admin” kommen nur Administrator – die Benutzer und anonyme kommen nur auf die “Default.aspx” im Root Verzeichnis – dies wurde über die ASP.NET Configuration vorgenommen.

Schritt 4: MasterPage einrichten

Auf jeder Seite wollen wir bei anonymen Nutzern einen “Anmelden” Button anzeigen oder bei bereits angemeldeten einen “Abmelden” Button – daher kommt dies in die MasterPage.
Zudem wollen wir jeden Nutzer “Willkommen” sagen und spezielle Links je nach Rolle ihm zur Verfügung stellen:

image

Das “LoginStatus” Control übernimmt den Login/Logout Part. Das “LoginView” Control bietet die Möglichkeit je nach Anmeldestatus informationen bereitzustellen:

image

Mithilfe des “LoginName” Control bekommen wir den Usernamen des momentan angemeldeten Nutzers raus.

Schritt 5: Login Page einrichten

Die Login.aspx ist Standardmäßig die Login Seite (ist in der web.config gespeichert) und muss natürlich ebenfalls noch mit einem Control gefüllt werden – das “Login” Control. Wenn man mit den normalen Aussehen leben kann, muss man hier auch nichts mehr machen. Falls man allerdings das design etwas stärker ändern möchte, muss man wie hier beschrieben z.B. ein Template anlegen.

image

Die Codebehinde sieht dann so aus:

image

Das wars eigentlich bereits. Wenn man diese Template Variante wählt, muss man dann natürlich um die ganze Funktionalität abzudecken, auch wie hier beschrieben die Elemente mit implementieren.

Sehr praktisch ist es auch, wenn ein nicht angemeldeter Nutzer z.B. die Adminseite besuchen möchte, wir automatisch auf die Login.aspx verlinkt, allerdings wird als Parameter die Zieladresse mitgegeben, sodass nach einem erfolgreichen Login man automatisch auf der “richtigen” Seite befindet:

image

Dafür ist diese Methode verantwortlich: FormsAuthentication.RedirectFromLoginPage.

Die technische Seite

Der angemeldete Nutzer ist in der Klassenhierarchie z.B. bei jeder Page oder HttpContext unter “User” zu finden:

image

Andere Teile finden sich auch in dem HttpSessionState:

image

Eine Sache zu den Controls

Die Login Controls renden oft nicht das, was man als normaler Webdesigner erwartet – dafür gibt es entweder den CSS Friendly Adapter oder man muss eigene Controls entwickeln, welche auf das Membership System aufsetzen.

Grundsatzfrage: Sollte ich das Membership verwenden oder was selber implementieren?

Das ist so eine nette Frage – wenn man sich in das System eingefunden hat, sieht man viele positive Sachen. Die Controls sind natürlich etwas garstig, aber auch das bekommt man in den Griff.

Wenn man selber eine Art Membership System realisiert, dann kann dies später zu einem Problem werden, da das Membership System ansich wie oben zu sehen ist, einiges im Hintergrund macht. Wozu selber so ein System dann realisieren? Dann lieber ein Custom Provider erstellen – dies wäre mein Rat zu dem Thema.

Weiterführende Links & Informationen

Scott Guthrie hat einige tolle Links zusammengefasst. Ein sehr gutes, 9 teiliges Tutorial gibt es zudem auch hier. Mein persönliches Lieblingsthema ASP.NET MVC kann man zudem ebenfalls mit dem Membership System (und den Controls) verheiraten: ASP.Net MVC Framework – Using Forms Authentication. Hier gibt es auch noch ein HowTo direkt auf der MSDN.

Es gibt auch direkt ein Buch was sich nur mit diesem Thema auseinandersetzt.

Download des Demoprojekts

Um das Demoprojekt auszuführen braucht ihr VS 2008 und in der web.config muss der Connection String noch angepasst werden. In der ReadMe.txt stehen die eingetragenen Nutzer samt Passwörter. Viel Spaß wünsch ich euch.

[ Download Democode ]

HowTo: ASP.NET Profile System mit Web Projects nutzen (Visual Studio 2005/2008)

ASP.NET 2.0 führte ein so genanntes “Membership” System ein – darunter sind allgemeine Benutzerregistrier-, Benutzerprofil- und Benutzerrollensystem gebündelt.

Insbesondere das Profilsystem hat mich heute etwas beschäftigt (auch wenn es etwas älter ist). Mit dem Profilsystem kann man einfach Benutzern bestimmte Attribute zuordnen, z.B. Name, Alter, Wohnort oder andere Verweise zu bestimmten Daten innerhalb der Applikation.

Das kann man natürlich auch selber in seiner DB zusammenbasteln – aber das ASP.NET Profile System bietet eine strenge Typsierung, sodass man in der Web.config folgendes anlegen kann:

<profile>
  <properties>
    <add name="PostalCode" />
  </properties>
</profile>

Und im Code hinter so aufrufen kann “Profile.PostalCode” – man kann auch direkt typen angeben, sodass man direkt festlegen kann, welcher Typ eine Profileigenschaft hat:

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
  <system.web>
    <profile>
      <properties>
        <add name="BirthDate" type="DateTime"/>
        <add name="FavoriteNumber" type="int"/>
        <add name="Comment" type="string"/>
        <add name="FavoriteColor" type="string" defaultValue="Blue"/>
        <add name="FavoriteAlbums"
             type="System.Collections.Specialized.StringCollection"
             serializeAs="Xml"/>
      </properties>
    </profile>
  </system.web>
</configuration>

Das ganze wird zudem mit der Membership Datenbank synchronisiert – dort gibt es direkt eine “Profile” Tabelle in dem diese Werte reingeschrieben werden:

image

Das ganze kann man nun noch beliebig erweitern oder eigene Profil-Provider schreiben – hier ein anderer guter Artikel.

Doch wie ruft man denn nun eigentlich die Profileigenschaften ab? Das was auf den MSDN Seiten immer “Profile.[PROPERTY]” verwendet wird, ist schlecht dokumentiert.

Jedenfalls erstellt VS im Hintergrund automatisch eine “ProfileCommon” Klasse die überall erreichbar ist (* hier kommt noch eine Anmerkung!)

So kommt man an die Profile Eigenschaften (System.Web.Profile Namespace):

ProfileCommon profile = HttpContext.Current.Profile
as ProfileCommon;

Danach kann man über “profile.” seine Properties aufrufen.

* – die Anmerkung

Das ganze funktioniert aber nur mit dem “Web Site” Modell (hier z.B. ein Beispiel von Scott) – in Web Applications wird die ProfileCommon nicht automatisch erstellt.
Scott Guthrie schrieb auf einer sehr alten Seite (ganz unten) seine Lösung: WebProfile Generator (Alternative: Die Klasse manuell erstellen)

Problem hier: Das ganze läuft unter Visual Studio 2008 bisher nicht (daher manuell erstellen :( ) – wird aber hoffentlich bald kommen. Auf der Codeplex Seite habe ich bereits einen Kommentar gefunden, kann daher wohl nicht mehr lange dauern.

Anmerkung gernell

Es gibt sicherlich  noch die ein oder andere Möglichkeit auf die Profildaten zuzugreifen, aber extra wegen dieser streng-typisierten und leichten Variante habe ich das einer Eigenentwicklung vorgezogen – schauen wir mal, wann ich dies bereuen werde ;)