Archiv

Archive for the ‘ASP.NET /AJAX /ASP.NET MVC’ Category

ASP.NET MVC 5 Paging selber implementiert

Bei meinem aktuellen Projekt, wollte ich das Paging in einer MVC Applikation selber, ohne Mithilfe der NuGet PagedList for MVC, realisieren.

Zuerst der Reihe nach

  1. Wie wird die erste Seite aufgerufen?
  2. Wie werden dann die folgenden Seiten aufgerufen?

Wie wird die erste Seite aufgerufen?

Zuerst schauen wir uns den HTML Markup des Linkes an, bei welchem wir zur entsprechenden Seite navigieren.


Wie ihr seht ist dass ein ganz normaler Querystring über http

Die Seite wird dann über folgende ActionMethode aufgerufen, wie auch die Seiten die dann folgen. Zum Beispiel Seite 2 etc.

Wie werden dann die folgenden Seiten aufgerufen?

            int toalPageCount = (context.Set<Skater>().Where(skater => skater.IsActive && skater.IsSEV).OrderBy(skater => skater.Lastname).Count() / 10);
            int pageCounter = 0;

            Dictionary<int, List<SkaterDTO>> pages = new Dictionary<int, List<SkaterDTO>>();
            List<SkaterDTO> DTO = new List<SkaterDTO>();                        

            if (selectedPage == 1)
            {
                context.Set<Skater>().OrderBy(skater => skater.Lastname).Where(skater => skater.IsActive && skater.IsSEV).Take(10).ToList().ForEach(skater =>
                {
                    var vm = new SkaterDTO(skater);
                    DTO.Add(vm);
                });
                pages.Add(toalPageCount, DTO.ToList());
                pageCounter++;                    
            }
            else if (selectedPage > 1)
            {
                if (selectedPage == toalPageCount)
                {
                    context.Set<Skater>().OrderByDescending(skater => skater.Lastname).Where(skater => skater.IsActive && skater.IsSEV).Take(10).ToList().ForEach(skater =>
                    {
                        var vm = new SkaterDTO(skater);
                        DTO.Add(vm);
                    });
                }
                else
                {
                    context.Set<Skater>().OrderBy(skater => skater.Lastname).Where(skater => skater.IsActive && skater.IsSEV).Skip(selectedPage * 10).Take(10).ToList().ForEach(skater =>
                    {
                        var vm = new SkaterDTO(skater);
                        DTO.Add(vm);
                    });
                }
                pages.Add(toalPageCount, DTO.ToList());
                pageCounter++;                                        
            }
                       

            return PartialView("Skaters", pages);

Die HTML Seite für die Darstellung des Resultates sieht dann wie folgt aus:

@model System.Collections.Generic.Dictionary<int, System.Collections.Generic.List<EislaufSektionWebUI.DTO.SkaterDTO>>

@{
    ViewBag.Title = "Läufer(innen)";
    Layout = "~/Views/Shared/_LayoutPage.cshtml";
}

@Html.ActionLink("Zurück", "Welcome", "Home")

<table class="table table-responsive table-bordered table-hover">

        <thead>
            <tr>
                <th>
                    Vorname
                </th>
                <th>
                    Name
                </th>
                <th>
                    Bestandene Tests
                </th>
                <th>
                    SEV
                </th>
                <th>
                    Geburtsdatum
                </th>
            </tr>
        </thead>
        <tbody>
            @{               
                var currentPage = int.Parse(HttpContext.Current.Request.QueryString["selectedPage"]);
                foreach (var skater in Model[currentPage-1].ToList())
                {
                    <tr>
                        <td>
                            @skater.Firstname

                        </td>
                        <td>
                            @skater.Lastname
                        </td>
                        <td>
                            @skater.CertificationLevel
                        </td>
                        <td>
                            @skater.IsSEV
                        </td>
                        <td>
                            @skater.DateOfBirth
                        </td>
                    </tr>
                }
            }
        </tbody>
    </table>

<nav>
    <ul class="pagination">
        <li>
            <a href="#" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>
        @{
            foreach (var key in Model.Keys)
            {
                var number = key + 1;
                <li>
                    <a href="/Person/GetSkaters?selectedPage=@number">@number</a>
                </li>
            }
        }
        <li>
            <a href="#" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
    </ul>
</nav>

Die Twitter Bootstrap „pagination“ Links werden dann wie folgt generiert:

<nav>
    <ul class="pagination">
        <li>
            <a href="#" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>
        @{
            foreach (var key in Model.Keys)
            {
                var number = key + 1;
                <li>
                    <a href="/Person/GetSkaters?selectedPage=@number">@number</a>
                </li>
            }
        }
        <li>
            <a href="#" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
    </ul>
</nav>

Ich hoffe euch hat der Beitrag gefallen und freue mich über Rückmeldungen oder konstruktive Kritik.

Advertisements

WCF Service über JQuery ansprechen leicht gemacht

Zur Zeit bin ich an einer Weiterbildung für die Verwendung von .NET im Grossfirmenumfeld. Ein Thema darin ist ist Windows Communication Foundation (kurz WCF genannt).

Da ich aus der Ecke der Webprogrammierer komme interessiert es mit primär, wie ich einen WCF-Service von einer Webseite her ansprechen kann.

Was brauchen wir denn für einen Service der auf der Basis der WCF für einen Webseitenzugriff konfiguriert ist?

Vorbereitungen

  1. Erstellen eines neues ASP.NET Projektes über den Menüpunkt „Neues Projekt erstellen…“
  2. Mit NuGet zuerst die JQueryUI und dann die JQuery
    Libraries hinzufügen
  3. Erstellen Sie einen Ordner Services in der Solution
  4. In den zuvor erstellten Service fügen Sie ein neues Item hinzu (Vorlage für einen
    WCF-Service) und benennen Sie diesen mit „ImageService“
  5. Visual Studio erstellt uns eine .svc und ein entsprechendes Interface ImageService.cs.
    Hier stehen noch nicht allzu viele Sachen drin sodass wir die DoWork Methode im
    Interface ein wenig anpassen. Nach dem Hinzufügen des Service sollte die Solution
    mit den aktuellen JQuery Referenzen ausgestattet sein und folgende .NET Dll’s als
    Referenz aufweisen:

    • System.Service.Model
    • System.Runtime.Serializatin

WCF Konfiguration

Soweit so gut. Wir sind bereit den WCF Service zu implementieren. Hierzu wird in die Solution noch ein Verzeichnis Images hinzugefügt, dass einfach mit den Beispiel Bildern von Windows befüllt wird. Zuerst fangen wir mit der Konfiguration der Web.Config für unseren Dienst an.

Konfiguration der Web.Config mit dem „WCF Service Configurator“ (Finden Sie unter Tools bei Visual Studio 2010)

  1. Erstellen eines neuen Services
  2. Auswahl der DLL von welcher her der Service generiert werden soll. Sollte im bin
    Verzeichnis keine DLL vorliegen so kann man dies mit dem „Erstellen der Solution“
    beheben
  3. Den Vertrag (Contract) können wir stehen lassen und weiter klicken.
  4. Da wir einen ImageService für’s Internet bereitstellen wollen, lassen wir die Konfiguration
    auf http sein.
  5. Auch weitere Optionen belassen wir auf dem Standard. Wir wollen einen einfachen
    ImageService ohne Duplex Funktion und lassen den Vorschlag des Assistenten so wie
    er ist.
  6. Zum Schluss erstellen wir die Endpunkt-Adresse über welche der Service erreicht
    werden soll.
  7. Wir haben einen neuen Service erstellt, wie uns der Assistent nun mitteilt.

Anpassen des WCF Service für unsere Bedürfnisse

Nun haben wir den Service deklarativ in der Web.Config erstellt. Nun müssen wir noch Anpassungen vornehmen, damit der Service auch mit einer WebHttpBinding angesprochen werden kann (das Programmiermodell http von WCF).

Anpassen des erstellten Service für die Verwendung mit ASP.NET (WCF Service Configurator).

  1. Selektieren Sie den zuvor erstellten Endpunkt
  2. Das Binding muss mit der DropDownListe auf webHttpBinding gewechselt werden.
  3. Nun müssen wir dem Binding noch eine Konfiguration zuordnen. Dies können wir tun
    indem wir den Punkt „Bindings“ auswählen und ein neues hinzufügen. ACHTUNG:
    Man muss das WebHttpBinding auswählen, damit die erstellte Konfiguration darauf
    wirkt.
  4. Wir geben der neu erstellen Konfiguration einen Namen und belassen die Standardwerte
    so wie sie sind (Die Ausnahmen werden nachstehend aufgelistet).

    Konfigurationselement Wert Grund
    ByPassProxyOnLocal True Lokale Adressen werden nicht über einenProxy-Server aufgelöst.
    CrossDomainScriptAccessEnabled True Zugriff von überall her möglich per Script (Internet Service).
  5. Nun wechseln wir wieder zu den Endpunkten und wählen unseren erstellten Endpunkt
    aus. Danach können wir in der Bindungskonfiguration die neu erstellte Bindung auswählen.

Den WCF Service zum Leben erwecken

Wir erstellen uns eine Global.asax die für das Starten und Beenden der WebApplikationen die entsprechenden Methoden bereitstellt. Interessant sind die Methoden Application_Start und die Application_End.

  1. Wir fügen unserem Webprojekt eine Global.asax mit dem Befehl Add new Item… hinzu.
  2. Damit wir webHttp verwenden können müssten wir System.ServiceModel.Web referenzieren
    damit wir ein Objekt vom Typ WebserviceHost erstellen können.
  3. Die Global.asax sieht dann wie folgt aus:

     using System; using System.ServiceModel.Web; using
                    Sample.WebHttpImageService.Services; namespace Sample.WebHttpImageService { public
                    class Global : System.Web.HttpApplication { ///
                    <summary>
            /// Host is declared here to not been removed by
            /// the carbage collector.
            /// </summary>
                    private static WebServiceHost host = new WebServiceHost(typeof(ImageService), new
                    Uri("http://localhost:8099")); ///
                    <summary>
            /// Occurs when application is staring up.
            /// </summary>
                    ///
                    <param name="sender"></param>
                    ///
                    <param name="e"></param>
                    protected void Application_Start(object sender, EventArgs e) { try { host.Open();
                    } catch (Exception exception) { host.Abort(); } } ///
                    <summary>
            /// Occurs at application shutdown.
            /// </summary>
                    ///
                    <param name="sender"></param>
                    ///
                    <param name="e"></param>
                    protected void Application_End(object sender, EventArgs e) { host.Close(); } } }
                    
  4. Startet man nun das Projekt kann unter Umständen die Fehlermedung kommen, dass man
    eine nicht registrierte URL versucht aufzurufen. Abhilfe schafft hier das Visual
    Studio als Administrator zu starten (generell mit WCF zu empfehlen).

  5. Nun wollen wir prüfen ob der Service mit der Methode GetImages angesprochen werden
    kann. Die machen wir indem wir http://localhost:8099/ImageService/GetImages in den
    Browser eingeben. Wir eine Fehlermeldung, die besagt dass die Methode nicht ausgeführt
    werden kann.

    Ist ja eigentlich logisch, denn unsere momentane Service Methode erwartet einen
    Parameter der über Web „GET“ übermittelt wird. Dies wurde noch nicht implementiert.

  6. Ist das Attribut im Interface angegeben können wir zum Beispiel mit dem Auftruf
    http://localhost:8099/ImageService/GetAllImages?path=Test einen Methodenaufruf machen
    und erhalten das folgende Beispiel Resultat.


Anpassen der WCF Methode GetImages

Nun wollen wir doch, dass der Service über einen AJAX Aufruf per JQuery angesprochen werden kann. Hierzu gehen wir wie folgt vor. Da die Service-Methode noch nicht so tut wie wir das gerne wollen, dekorieren wir die Methode im Interface mit dem Attribute WebGet. Das Interface sieht nun wie folgt aus:

[ServiceContract]
public Interface IImageService{
[OperationContract]
[WebGet]
public List<string> GetImages(string path);
}

Die Implementierungsklasse sieht dann so aus:

public class ImageService : IImageService{
public List<string> GetImages(string path){
List<string> result = new List<string>(){ "Hello WCF"};
return result;
}
}

Clientseitige Implementierung

Nun kommen wir auf die Clientseite. Wir fügen, falls es nicht schon getan wurde, ein Default.aspx WebForms zur Solution hinzu.

Hierzu erstellen wir uns unter dem Verzeichnis (Scripts) ein Unterverzeichnis mit dem Namen „Custom“ und fügen uns dort eine ImageService.js Datei hinzu, die folgenden Inhalt aufweist. (Vergessen Sie nicht die JQuery Scriptreferenz im Header der Default.aspx hinzuzufügen).

Unser erstelltes Clientscript sieht dann so aus:

$(document).ready(function () {
    var imagesUri = "http://localhost:7777/Images/";

    jQuery.support.cors = true;

    $.ajax({

        type: "GET",
        url: "http://localhost:8099/ImageService/GetImages?path=" + imagesUri,
        dataType: "text",
        contentType: "application/text; charset=utf-8",
        success: function (message) {
            //$('#Result').append(message);
            var xmlDocument = jQuery.parseXML(message);
            var images = xmlDocument.getElementsByTagName("string");

            for (var index = 0; index <= images.length - 1; index++) {

                $('#Result').append("<img src=\'" + images[index].textContent + "\' />");

            }
        },
        error: function (message) {

        }
    });

});

Die Default.aspx braucht dann nur noch eine Script-Referenz auf diese Datei.

<head runat="server">
    <title>Zugreifen auf WCF mittels JQuery</title>  
    <script type="text/javascript" language="javascript" src="Scripts/jquery-1.7.1.js"></script>  
    <script type="text/javascript" language="javascript" src="Scripts/Custom/ImageService.js" ></script>
</head>

Der httpQuery muss mit dem Parameter der im Inteface für die Servicedefinition festgelegt worden ist übereinstimmen. Nun ist es nicht wirklich ansprechend ein Hallo reinzugeben und dann als Resultat Hallo WCF, mit den geschweiften Klammern zu erhalten.

Wichtig hierfür ist das JQuery für CrossDomain zugriff konfiguriert wird. Dies geschieht wie folgt:

	jQuery.support.cors = true;

Nun da WCF und ASP.NET nicht im gleichen Kontext beheimatet sind kann die Grundlage beider genommen werden. Beide bauen auf System.Web.Hosting auf. Folgenes Beispiel lässt alle Bilder die vom Pfad übergeben worden sind mit dem DirectoryInfo suchen und finden.

using System;
using System.Collections.Generic;
using System.IO;
using System.Web.Hosting;
using System.Linq;

namespace Sample.WebHttpImageService.Services
{
    /// <summary>
    /// Get's the images from the path given by parameter.
    /// </summary>
    /// <param name="path">ImagePath to look for images.</param>
    public class ImageService : IImageService
    {
        public string[] GetImages(string path)
        {
            List<string> result = new List<string>();
            
            Uri uri = new Uri(path, UriKind.Absolute);
            
            FileInfo[] images = new DirectoryInfo(HostingEnvironment.MapPath(uri.LocalPath)).GetFiles();

            images.ToList().ForEach(image =>
                                        {
                                            string imageUrl = string.Format(@"{0}{1}", path, image.Name);
                                            result.Add(imageUrl);
                                        });

            return result.ToArray();
        }   

Unser Image Service muss so dann nicht im ApsNetCompatibilityMode betrieben werden und wir können trotzdem auf Files auf dem WebServer zugreifen. Nähere Details können hier gefunden werden WCF Service and ASP.NET

Zu guter Letzt Verarbeiten wir Client-seitig das XML das wir vom Server zurück erhalten und zeigen die Bilder an.

Das Bilder anzeigen haben wir ja in die ImageService.js ausgelagert.

Fazit

Mit eigentlich ganz wenig Aufwand ist es möglich einen Service zur Verfügung zu stellen der von allen Interessierten konsumiert werden kann. Trickreich an den ganzen Recherchen war auf jeden Fall das Zusammenspiel zwischen ASP.NET und der WCF die ja nicht im gleichen Kontext laufen, ohne dass man das AspNetCompatibilityReruirement Attribut braucht, oder den WCF-Service im Kompatibilitätsmodus laufen lässt. Weiter werde ich versuchen in den nächsten Wochen ein wenig mehr um die Daten die ausgetauscht werden, zu beleuchten.

Wenn Ihnen dieser Artikel gefallen hat, würde ich mich über einen Kick freuen und bin auch empfänglich für Rückmeldungen und Kritik.

kick it on dotnet-kicks.de

Heirate mich (ASP.NET und JQuery Accordion)

Was ganz nützlich für die Darstellung von Daten ist, ist das Accordion, welches ja auch bereits im AjaxControlToolkit vorkommt.

Nun die meisten werden das Ganze sicherlich rein nativ client-seitig mit JQuery und der Herumschlagerei von Div-Elementen verwendet haben.

Meine Überlegung war es, in Abhängigkeit zu einer Quelle (IEnumerable) die Accordion-Items generieren zu lassen.

Wie bin ich vorgegangen?

Als erstes habe ich mir das Markup eines Demo-Accordions auf der Webseite von JQuery angesehen.

Da es im .NET die Klasse HtmlGeneric gibt kann man mit dieser mit ein wenig Geschick viel anstellen

Also wir brauchen folgendes:

  • Eine ASP.Seite mit einem Div-Element dass den Container für das Accordion darstellt
  • Als nächstes brauchen wir eine Klasse, die uns den Accordion Markup generiert
  • Und last but not least ein Model Element (Klassenelement aus unserem Modell
  • 1. Erstellen der ASPX Seite

    Der nachfolgende Markup-Code ist keine Magie und ziemlich schlank

    <%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="AspNetWebFromJQueryUI.Index" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
    <link type="text/css" href="Content/themes/base/jquery.ui.all.css" rel="Stylesheet" />	
    <script type="text/javascript" src="/Scripts/jquery-1.6.4.js"></script>
    <script type="text/javascript" src="/Scripts/jquery-ui-1.8.16.js"></script>
    </asp:Content>
    <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <script>
        $(function () {
            $("#Accordion").accordion();
        });
    	</script>
    <div id="ResultDiv" runat="server">
    </div>
    
    </asp:Content>
    

    Einzig das Referenzieren von JQuery und JQueryUI sollte beachtet werden. Hierzu findet man auf JQueryUI die entsprechende Anleitung im Get started Teil.

    2. Den Helper für das Accordion

    Nun kommen wir zum Helper für die Erstellung des Markups für das Accordion, der wie folgt aufgebaut ist:

    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Web.UI;
    using System.Web.UI.HtmlControls;
    
    namespace AspNetWebFromJQueryUI.Controls
    {
        /// <summary>
        /// Responsible for creating an accordion base
        /// for using it with JQueryUI
        /// </summary>
        public static class AccordionHelper
        {
            /// <summary>
            /// Returns the complete HTML code for the accordion.
            /// </summary>
            /// <typeparam name="T">The model type.</typeparam>
            /// <param name="datasource">The items to show in the accordion</param>
            /// <see cref="http://jqueryui.com/demos/accordion/"/>
            /// <returns>The HTML Markup for an accordion in JQuery manner</returns>
            public static HtmlGenericControl CreateAccordion<T>(IEnumerable<T> datasource)
            {
                HtmlGenericControl accordion = new HtmlGenericControl {TagName = "div", ClientIDMode = ClientIDMode.Static, ID = "Accordion"};
                accordion.InnerHtml = CreateContentDiv(datasource);
    
                return accordion;
            }
    
            /// <summary>
            /// Creates the content div's for putting the model
            /// content to it.
            /// </summary>
            /// <typeparam name="T">The model item (for example from the businessmodel)</typeparam>
            /// <param name="datasource">The items that are the datasource for the accordion</param>
            /// <returns>The HTML string for the accordion</returns>
            private static string CreateContentDiv<T>(IEnumerable<T> datasource)
            {
                StringBuilder sb = new StringBuilder();
    
                if (datasource != null)
                    for (int index = datasource.Count() - 1; index >= 0; index--)
                    {
                        if (index == 0)
                        {
                            sb.AppendLine(string.Format("<h3><a href=\"#\">{0}</a></h3>", CreateTitleElement(datasource.ToList()[index])));
                            sb.AppendLine(string.Format("<div>{0}</div>", CreateContentItemFromModel(datasource.ToList()[index])));
                        }
                        else
                        {
                            sb.AppendLine(string.Format("<h3><a href=\"#\">{0}</a></h3>", CreateTitleElement(datasource.ToList()[index])));
                            sb.AppendLine(string.Format("<div>{0}</div>", CreateContentItemFromModel(datasource.ToList()[index])));
                        }
                    }
    
                return sb.ToString();
            }
    
            /// <summary>
            /// Foreach property in the model item it will be crated a desing
            /// like label: value of the property.
            /// </summary>
            /// <typeparam name="T">The model object</typeparam>
            /// <param name="model">The model item to treat</param>
            /// <returns>The content for the contentItems in the accordion</returns>
            private static string CreateContentItemFromModel<T>(T model)
            {
                StringBuilder sb = new StringBuilder();
                model.GetType().GetProperties().ToList().ForEach(property => sb.AppendLine(string.Format(@"{0}: {1} <BR/>", property.Name, property.GetValue(model, null))));
    
                return sb.ToString();
            }
    
            /// <summary>
            /// Takes the first property of an object an set's it 
            /// as title.
            /// </summary>
            /// <typeparam name="T">The model object</typeparam>
            /// <param name="model">The model item to treat</param>
            /// <returns>The content for the title in the accordion</returns>
            private static string CreateTitleElement<T>(T model)
            {
                StringBuilder sb = new StringBuilder();
                PropertyInfo info = model.GetType().GetProperties().FirstOrDefault();
                string title = string.Format(@"{0}: {1}", info.Name, info.GetValue(model, null));
    
                return title;
            }
        }
    }
    

    Was macht der Code?

    • Er erstellt das Grundgerüst in welchem die Items reingepflanzt werden.
    • Er erstellt einen Titel aus dem ersten Property des Modells und setzt diesen mit dem Wert des ersten gefundenen Properties von der Modellklasse
    • Ist alles erledigt, gibt er den gesamten erstellen Markup zurück

    3. Die Modellklasse

    Meistens findet ja so ein Accordion bei einem Newsreader Verwendung. Eine solches News-Items hat die folgenden Eigenschaften:

    • Titel
    • Inhalt
    • Veröffentlichungsdatum

    Nachstehend die sehr einfache Modellklasse für ein News-Item.

    using System;
    
    namespace AspNetWebFromJQueryUI.Model
    {
        public class NewsItem
        {
            public string Title { get; set; }
    
            public string Content { get; set; }
    
            public DateTime PublishDate { get; set; }
    
        }
    }
    

    Wirklich ganz trivial…

    4. Einbindung in unsere Seite

    Die Einbindung in die Seite geschieht dann wie folgt:

    • ResultatDiv der Seite mit einem runat=“server“ versehen
    • Im Codebehind dem Control das generierte Accordion hinzufügen.
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using AspNetWebFromJQueryUI.Controls;
    using AspNetWebFromJQueryUI.Model;
    
    namespace AspNetWebFromJQueryUI
    {
        public partial class Index : System.Web.UI.Page
        {
            protected void Page_Init(object sender, EventArgs e)
            {
                //Create dummyData
                List<NewsItem> datasource = new List<NewsItem>();
    
                for (int index = 0; index < 10; index++)
                {
                    datasource.Add(new NewsItem() { Content = string.Format(@"Neuigkeitsinhalt {0}", index.ToString()), 
                                                    Title = string.Format(@"Neuigkeit {0}", index.ToString()), 
                                                    PublishDate = DateTime.Now.Subtract(new TimeSpan(index, index, index)) });
                }
                
                this.ResultDiv.Controls.Add(AccordionHelper.CreateAccordion(datasource.OrderByDescending(n => n.PublishDate)));
                
            }
        }
    }
    

    Somit wird beim Starten, alles in einem schönen von JQuery verwalteten Accordion gerendet.

    ACHTUNG: Reflection funktioniert nicht in Abhängigkeit zu den .NET Sicherheitseinstellungen, daher muss zuerst überprüft werden, ob mit Reflection gearbeitet werden kann.

    Für Anregungen und Fragen würde ich mich auf einen Kommentar freuen

    kick it on dotnet-kicks.de

Vorlagen mal anders…

… und die Möglichkeit auch ohne JQuery Templates auszukommen. Wie soll man das verstehen? Nun ganz einfach, wenn wir eine XML basierte Daten getriebene Webanwendung haben, so ist es nicht immer notwendig mit JQuery Templates das Verschönern der Seite vorzunehmen. Hierzu kann XML /XSL(T) ein wahres Traumpaar sein. Anhand von drei Beispielen soll dann auch gezeigt werden wie es mit dem XMLControl von ASP.NET, rein Client seitig und rein Code-behind bewerkstelligt wird.

XML Data Control

Zuerst erstellen wir uns eine neue ASP.NET WebForms Anwendung und fügen in unsere Seite das XML Data Control ein.

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeBehind="Default.aspx.cs" Inherits="XMLDataDrivenWeb._Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>
        XML Driven Web Development
    </h2>
    <p>
        XML DataControl to apply XSL to the XML Document.

        <asp:Xml ID="XmlContainer" runat="server" 
            DocumentSource="~/App_Data/KontaktAngaben.xml" 
            TransformSource="/App_Data/KontaktFormatierung.xsl" onload="XmlContainer_Load" ></asp:Xml>
    </p>
</asp:Content>

Damit wir das Control mit den notwendigen Daten füttern können, erstellen wir zwei Dateien im App_Code Ordner.

Die XML Datendatei
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="KontaktFormatierung.xsl"?>
<Kontakangaben>
    <Person>
        <Salutation>Herr</Salutation>
        <FirstName>Hans</FirstName>
        <LastName>Müller</LastName>
        <Street>Müllerstrasse 14</Street>
        <City>3205 Müllhausen</City>
        <Phone>0132542254</Phone>
        <Phone>0123655447</Phone>
        <Role>Präsident</Role>
        <EMail href="hans.mueller@muellergmbh.com">Müller Hans</EMail>
    </Person>
    <Person>
        <Salutation>Frau</Salutation>
        <FirstName>Heidi</FirstName>
        <LastName>Müller</LastName>
        <Street>Müllerstrasse 14</Street>
        <City>3205 Müllhausen</City>
        <Phone>0766778939</Phone>
        <Phone>0324546688</Phone>
        <Role>Vizepräsidentin</Role>
        <EMail href="heidi.mueller@muellergmbh.com">Heidi Müller</EMail>
    </Person>
</Kontakangaben>

Die dazugehörige XSL(T) Datei sieht dann wie folgt aus.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
    <!-- Define the outputtype -->
    <xsl:output method="html"/>
    <xsl:template match="/*">
        <table class="tableStyle">
            <tr>
                <th>Kontaktangaben</th>
                <th>Rolle</th>
                <th>Telefon(e)</th>
                <th>E-Mail</th>
            </tr>
            <xsl:for-each select="Person">
                <tr>
                    <td>
                        <xsl:value-of select="FirstName"/> <xsl:value-of select="LastName"/>
                        <br/>
                        <xsl:value-of select="Street"/>
                        <br/>
                        <xsl:value-of select="City"/>
                    </td>
                    <td>
                        <xsl:value-of select="Role"/>
                    </td>
                    <td>
                        <ul>
                            <xsl:for-each select="./Phone">
                                <li>
                                        <xsl:value-of select="."/>                            
                                </li>
                            </xsl:for-each>
                        </ul>
                    </td>
                    <td>
                        <a>
                            <!-- The xml attribute sets the value for the <a> tag and adds an attribute to it-->
                            <xsl:attribute name="href">mailto:<xsl:value-of select="../Person/EMail/@href"/></xsl:attribute>
                            <xsl:value-of select="../Person/FirstName"/>
                            <xsl:value-of select="../Person/LastName"/>
                        </a>                                                                                                                               
                    </td>
                </tr>
            </xsl:for-each>
        </table>
    </xsl:template>
</xsl:stylesheet>

Wichtig ist dass der output mode auf HTML gestellt wird, ansonsten wird es mit dem Link in der E-Mail Adresse schwierig diesen korrekt darzustellen.

Der Ouput wird dann automatisch vom XML Control gerendert.

Reine clientseitige Verarbeitung

Eine weitere Möglichkeit, für alle die JavaScript über alles lieben, ist mit JQuery die Transformation vorzunehmen.

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="XMLDataDrivenWeb.Index" %>
<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">    
    <script type="text/javascript" language="javascript">
        var xml = null;
        var xsl = null;

        $(document).ready(function () {

            $.ajax({
                type: "GET",
                url: "XMLData/KontaktAngaben.xml",
                data: "{}",
                contentType: "application/xml; charset=utf-8",
                dataType: "xml",
                success: function (msg) {
                    xml = msg;
                },
                error: function (msg) {
                    alert(msg);
                }
            });

            $.ajax({
                type: "GET",
                url: "XMLData/KontaktFormatierung.xsl",
                data: "{}",
                contentType: "application/xml; charset=utf-8",
                dataType: "xml",
                success: function (msg) {
                    xsl = msg;
                    TransformXml();
                },
                error: function (msg) {
                    alert(msg);
                }
            });
        });

        function TransformXml() {

            $('#ResultDiv')[0].innerHTML = xml.transformNode(xsl);
        }
    </script>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<div id="ResultDiv">

</div>
</asp:Content>
Code behind Bearbeitung

Zu guter letzt kann alles natürlich mit C# erledigt werden, was eigentlich auch keine Hexerei darstellt.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml.Linq;
using System.Xml;
using System.Xml.Xsl;
using System.IO;
using System.Text;

namespace XMLDataDrivenWeb
{
    public partial class PureDotNet : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            GetDocument();
        }

        private void GetDocument()
        {
            XmlDocument x = new XmlDocument();
            x.Load(XmlReader.Create(Server.MapPath(@"/App_Data/Kontaktangaben.xml")));

            XslCompiledTransform transform = new XslCompiledTransform();
            transform.Load(XmlReader.Create(Server.MapPath(@"/App_Data/KontaktFormatierung.xsl")));
            transform.Transform(x.BaseURI, Server.MapPath(@"/App_Data/Result.xml"));

            // Loading the generated result file
            string[] xmlResult = File.ReadAllLines(Directory.GetFiles(Server.MapPath(@"/App_Data")).ToList().Where(f => f.ToLower().Contains("result")).FirstOrDefault());
            
            xmlResult.ToList().ForEach(s =>
                                           {
                                               ResultDiv.InnerHtml += s;
                                           });
            
        }
    }
}

Zu guter letzt soll das Ganze auch ein wenig ansprechend aussehen. Dies machen wir wie gewohnt über CSS. Die folgenen Styles werden nachfolgend aufgeführt:

th 
{
    font-size: x-large;
    font-family: cursive;
    background-color: silver;  
    padding: 2px;    
}

td 
{
    font-size:medium;
    font-style: normal;
    border: thin solid black;
}

Wie wir sehen gibt es immer mehrere Wege nach Rom und das Ganze, ach Microsoft will C# etc. abschaffen und nur noch Richtung JavaScript gehen, eher übertrieben. Es kommt immer noch auf uns Entwickler an wie wir eine Lösung zu einem Problem finden.

Anhand dieses einfachen Beispiels sehen wird, dass es möglich ist für ein Problem die eine oder andere Lösung zu wählen, je nachdem was einem beliebt.

Für Anregungen und Kritik bin ich gerne offen

kick it on dotnet-kicks.de

ASP.NET Optimization Bundling

Im ganzen HTML 5/ CSS3 etc. Gepredige habe ich doch noch was gefunden was im ASP.NET 4.5 kommen wird. Nun wenn man NuGet mal durchsucht, dann wird man es auch für ASP.NET 4.0 finden. Das Optimization Bundling (Im .NET 4.0 wird es mit Microsoft.Web.Optimization, und dann später mit System.Web.Optimization referenziert). Das Video ASP.NET 4.5 loves HTML5, CSS3 & JavaScript habe ich als sehr informativ empfunden, obwohl nicht wirklich viel neues kommt, oder die Änderunge nicht gerade revolutionär sind.

Damit ich das Pack auch produktiv nutzen kann, habe ich es mal analog dem Video nachgespielt, damit ich, falls ich in meinem gesetzten Alter, nicht mehr alles auswendig weiss, nachlesen kann. Also anfangen, ich von meiner Warte aus entwickle immer noch mit ASP.NET WebForms entgegen dem Hype zu MVC. Also starten wir wie folgt, indem wir eine neue WebForms Solution erstellen.

Kurzübersicht was wir alles machen werden

  • Erstellen einer ASP.NET 4.0 Solution
  • Installation des Optimization Bundling per NuGet
  • Analyse Vorher /Nachher
  • Fazit

Also fangen wir damit an:

Erstellen einer ASP.NET Solution

Wir starten unser Visual Studio 2010 (Express/Professional/Premium/Ultimae), wählen die Rubrik Web und dann die Vorlage für ASP.NET WebForms. Ein Klick darauf und unsere Solution ist erstellt.

Installieren des ASP.NET Optimization – Bundling

Ist eigentlich keine Hexerei und kann bequem über NuGet erfolgen, dass dann alle notwendigen Schritte übernimmt, und auch die Referenzen im Projekt richtig setzt.

Nach der Installation ist alles bereit, damit wir unsere ASP.NET Lösung optimieren können

Analyse Vorher /Nachher

In diesem kleinen Beispiel hole ich per XHTML-Request drei Personen aus meinem Model und zeige diese an (der XHTML Request kann in seiner Dauer variieren, da interessiert uns die Zeit primär erstmal nicht.) Starten wir die jungfräuliche Solution und drücken im IE9 die Developer Tools mit F12 sehen wir folgende Verkehr:

Interssant sind folgende zwei Punkte

  • /Styles/Site.css
  • /Scripts/jquery-1.4.4.min.js

Nun, da wir das Optimization Pack bereits mit NuGet installiert haben, entfällt eine Referenz darauf, denn diese ist bereits vorhanden. Was wir noch machen müssen ist in der Global.asax dafür sorgen, dass die Dateien auch wirklich gebündelt übertragen werden.

        void Application_Start(object sender, EventArgs e)
        {
            BundleTable.Bundles.EnableDefaultBundles();
            Bundle myBundle = new Bundle("~/Scripts", typeof(JsMinify));
            myBundle.AddFile("~/Scripts/jquery-1.4.4.min.js");
            myBundle.AddFile("~/Scripts/PersonScript.js");
        }

Anschliessend passen wir in der Site.Master die Referenz auf JQuery an:

Vorher:

<script type="text/javascript" src="Scripts/jquery-1.4.4.min.js"></script>

Nachher:

<script type="text/javascript" src="Scripts/js"></script>

Anschliessend starten wir unsere Solution wieder und analysieren mit den Developer Tools nocheinmal den Netzwerk-Verkehr. Und siehe da, die zwei vorherigen Kandidaten (Scripts und CSS) haben einen kleinen Hypersprung ausgefürht

Fazit

Wir haben einen Performance Anstieg erhalten, der unsere Webseite schneller übertragen lässt. In diesem sehr, sehr kleinen Beispiel und auch dem localhost mögen die Einsparungen vielleicht nicht enorm sein, betrachtet es man aber so, dass viele Wege zu einer Webseite führen können und nicht immer alles übertragen wird (Package los), dann ist dies sicher ein guter Weg die Daten zu komprimieren.

Bei sehr vielen JavaScript- und CSS-Dateien lohnt es sich auf alle Fälle. Die Demo Lösung kann hier heruntergeladen werden. (Das MVC Beispiel wird im Video erläuter, sodass ich dies nicht erstellt habe.)

kick it on dotnet-kicks.de

Dynamisch generiertes Bilder-Menu erweitern mit JQueryUI

Im letzten Blogbeitrag wurden die Bilder im Verzeichnisbaum in das Menu gerendert (also die Struktur). Damit man noch ein wenig mehr Pepp in das Ganze bringen kann, ist es möglich JQueryUI zu verwenden. Zu diesem Zweck habe ich mir den Modal Dialog ausgesucht, dass wenn der Benutzer ein Bild, das verkleinert dargestellt wird, in einem Dialog angezeigt bekommt.

Was ist denn da alles notwendig?

Eigentlich nicht ganz viel:
1.) Man lade eines der vielen Themen herunter, dass von JQueryUI schon zur Verfügung gestellt wird.
2.) Grundlage bildet der vorherige Blogpost „Dynamisch generiertes Bilder-Menu“. Die Source kann hier herunter geladen werden.
Wie kommen wir denn nun zu dem modalen Bild

Als erstes passen wird den Menu-Click Event an. Hier werden dann dynamisch anhand der Anzahl Bilder die IMAGE-ASP.NET Controls in das entsprechende DIV gerendert.

        /// <summary>
        /// If menu is clicked the image will be rendered as image tag into the site
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void GalleryMenu_ItemClick(object sender, MenuEventArgs e)
        {
            MenuItem menuItem = (MenuItem)e.Item;
            string path = string.Format(@"Images/{0}", menuItem.ValuePath);
            DirectoryInfo directoryInfo = new DirectoryInfo(Server.MapPath(path));
            FileInfo[] files = directoryInfo.GetFiles("*.jpg");

            foreach (var picture in files)
            {
                Image image = new Image();
                image.Height = 200;
                image.Width = 200;
                image.ClientIDMode = System.Web.UI.ClientIDMode.AutoID;
                image.ID = string.Format(@"Image_{0}", picture.Length);
                image.ImageUrl = string.Format("{0}/{1}", path, picture.Name);
                image.Attributes.Add("onclick", string.Format(@"ShowImageInOriginalSize('{0}')", image.ClientID));
                ImageResultDiv.Controls.Add(image);
            }
        }

Das Ganze kann dann noch mit ein wenig JavaScript (JQuery) gepfeffert werden und sie dann auch entsprechend schön aus (sofern man das entsprechende Thema anhand der JQueryUI Hilfe eingebunden hat).

    <script type="text/javascript">
        function ShowImageInOriginalSize(imageClientId) {
            var image = $('#' + imageClientId + '');

            var modalPopUpImage = $('#ModalPopUpImage');
            modalPopUpImage[0].width = image[0].naturalWidth;
            modalPopUpImage[0].height = image[0].naturalHeight;
            modalPopUpImage[0].src = image[0].src;

            $('#ModalPopUp').dialog({ modal: true, height: image[0].naturalWidth, width: image[0].naturalWidth, maxHeight: image[0].naturalHeight, maxWidth: image[0].naturalWidth });
        }
    </script>

ACHTUNG:Die Stolperfalle, wenn man das angeklickte Bild dem ModalpopUp-Extender übergeben hat, dann wird es zwar angezeigt, aber nicht mehr in die Source (dort wo es angeklickt worden ist) „zurück“ gelegt. Aus diesem Grund ist es wichtig ein modales Bild zu haben dass die Höhe und die Breite als 0 aufweist und erst, wenn der Dialog angezeigt wird, in die entsprechende Grösse verändert wird.

<body>
    <form id="form1" runat="server">
    <div runat="server"  id="ResultDiv">
    <div style="float:left; margin:5px;">
        <asp:Menu ID="GalleryMenu" runat="server" RenderingMode="List" 
            OnMenuItemClick="GalleryMenu_ItemClick">
        </asp:Menu>
        </div>
        <div id="ImageResultDiv" runat="server" style="float:left; position:relative; margin-left:200px; margin-top:5px;">            
        </div>
    </div>
    </form>
    <div id="ModalPopUp" style="margin-bottom:2px;">
        <img id="ModalPopUpImage" src="#" alt="Alternate Text" width="0" height="0" />
    </div>
</body>
</html>

Somit kann das „Fake“-Bildchen versteckt und spielt erst wieder eine Rolle, wenn es gebraucht wird. Für Anregungen und Kritik bin ich offen und hoffe auf Rückmeldungen.

kick it on dotnet-kicks.de

Dynamisch generiertes Bilder-Menu

In einem aktuellen Projekt bin ich vor der Herausforderung gestanden, dass die Bilder in einer Navigationsansicht angesteuert werden können sollen. Man musste zuerst die Bilderquellen sortieren und gemäss folgender Struktur speichern (gem. Webempfehlungen existiert in einer Webseite zu 99% immer ein Order Images). Dieser ist mein Ausgangspunkt und darunter kann, unter den Umständen dass im Pfad nicht mehr als 255 Zeichen sind, eine Struktur erstellt werden die logisch erscheint. Ich bin hier folgendermassen vorgegangen:

1. Welche Bilder kann ich einer Kategorie zuordnen (meine im Dummy Projekt sind Events und Diverse).
2. Wann wurden die Bilder aufgenommen? (In welchem Jahr?)
3. In welchem Monat wurden die Bilder aufgenommen?
4. Da jedes Jahr immer 12 Monate hat, sieht jedes Jahr immer gleich aus 🙂

Die Struktur ist im folgenden Bild ersichtlich:

Ordnerstruktur ohne Inhalt

Ordnerstruktur mit Inhalt

Das Markup für das ASP.NET Menu sieht folgendermassen aus (das Rendern wurde als unordered list im Rendermode angegeben, der Grund hierfür ist, dass beinahe jedes CSS basierte Menu auf eine Unordered List basiert).

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="DynamicImageGalleryMenu._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></title>
</head>
<body>
    <form id="form1" runat="server">
    <div runat="server"  id="ResultDiv">
    <div style="float:left; margin:5px;">
        <asp:Menu ID="GalleryMenu" runat="server" RenderingMode="List" 
            OnMenuItemClick="GalleryMenu_ItemClick" BackColor="#E3EAEB" 
            DynamicHorizontalOffset="2" Font-Names="Verdana" Font-Size="0.8em" 
            ForeColor="#666666" StaticSubMenuIndent="10px">
            <DynamicHoverStyle BackColor="#666666" ForeColor="White" />
            <DynamicMenuItemStyle HorizontalPadding="5px" VerticalPadding="2px" />
            <DynamicMenuStyle BackColor="#E3EAEB" />
            <DynamicSelectedStyle BackColor="#1C5E55" />
            <StaticHoverStyle BackColor="#666666" ForeColor="White" />
            <StaticMenuItemStyle HorizontalPadding="5px" VerticalPadding="2px" />
            <StaticSelectedStyle BackColor="#1C5E55" />
        </asp:Menu>
        </div>
        <div id="ImageResultDiv" runat="server" style="float:left; margin:5px;">            
        </div>
    </div>
    </form>
</body>
</html>

Die Codebehind sieht dann folgendermassen aus und durchsucht ab dem Ordner Image alle darunter liegenden. Sind keine Ordner mehr vorhanden, werden die Bilder aufgelistet. Bei einem Menu_Click wird dann die ValueData-Eigenschaft des MenuItems ausgelesen und dann der Dateiname des Bildes hinzugefügt.

Page_Load Event
        protected void Page_Load(object sender, EventArgs e)
        {
            // Do only if no postback is occured the look up for the gallery
            // folders.
            if (!IsPostBack)
            {
                // Defining the root folder the building the menu.
                string path = Server.MapPath(@"~/Images");
                ListFoldersRecursivly(new DirectoryInfo(path));

                GalleryMenu.Items.OfType<MenuItem>().OrderBy(menuItem => menuItem.Text);
            }
        }
ListFoldersRecursivly
        /// <summary>
        /// Walk down the directory three to build up the asp.net menu
        /// </summary>
        /// <param name="directoryInfo"></param>
        private void ListFoldersRecursivly(DirectoryInfo directoryInfo)
        {
            DirectoryInfo[] directories = directoryInfo.GetDirectories();
            MenuItem rootMenuItem = null;

            foreach (var directory in directoryInfo.GetDirectories())
            {
                // In case that the root folder for our images is "Images" then
                // the starting node is images an the following nodes are the root nodes
                // for all the other ones.
                if (directory.Parent.Name.Equals("Images"))
                {
                    MenuItem menuItem = new MenuItem();
                    menuItem.Text = directory.Name;
                    GalleryMenu.Items.Add(menuItem);
                }

                if (directory.GetDirectories().Count() > 0)
                {
                    rootMenuItem = GalleryMenu.Items.Cast<MenuItem>().Where(item => item.Text.Contains(directory.Parent.Name)).FirstOrDefault();
                    if (rootMenuItem != null)
                    {
                        rootMenuItem.ChildItems.Add(new MenuItem() { Text = directory.Name });
                    }
                    ListFoldersRecursivly(directory);
                }
                else
                {
                    // Getting the root item (in case the current directory has no childdirectories jump up to levels to compare with the ValuePath of the menuItem
                    MenuItem rootItem = GalleryMenu.Items.Cast<MenuItem>().Where(item => item.ValuePath.Contains(directory.Parent.Parent.Name)).FirstOrDefault();

                    // One the rootItem was found, for all childitems we must look up similar to the root item the corresponding childitem to add the child directory
                    MenuItem subRootItem = rootItem.ChildItems.Cast<MenuItem>().Where(item => item.ValuePath.Contains(directory.Parent.Name)).FirstOrDefault();

                    if (subRootItem != null)
                    {
                        subRootItem.ChildItems.Add(new MenuItem() { Text = directory.Name });
                    }
                }
            }
        }

Zu guter Letzt ist noch der Click-Event des MenuItems zu erwähnen, der dann mit einem StringBuilder einen HTML-Image Tag in das angegebenen DIV schreibt.

Gallery MenuItem Click Event
        /// <summary>
        /// If menu is clicked the image will be rendered as image tag into the site
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void GalleryMenu_ItemClick(object sender, MenuEventArgs e)
        {
            MenuItem menuItem = (MenuItem)e.Item;
            string path = string.Format(@"Images/{0}", menuItem.ValuePath);
            DirectoryInfo directoryInfo = new DirectoryInfo(Server.MapPath(path));
            FileInfo[] files = directoryInfo.GetFiles("*.jpg");

            StringBuilder sb = new StringBuilder();

            foreach (var picture in files)
            {
                sb.AppendLine(string.Format("<img src=\"{0}\" alt=\"Alternate Text\" style=\"width:400px; height:400px\" /><BR />", string.Format(@"{0}/{1}/", path, picture.Name)));
            }

            ImageResultDiv.InnerHtml = sb.ToString();

        }

Die Sample Solution kann hier heruntergeladen werden. Für Kritiken und Verbesserungsvorschläge, oder kick, würde ich mich freuen.

kick it on dotnet-kicks.de