Laden...

Forenbeiträge von MartinE Ingesamt 49 Beiträge

07.03.2006 - 13:42 Uhr

Guten Tag.

Verwenden Sie immer IDbParameter (SqlParameter, OleDbParameter, OdbcParameter), um Werte an einen SQL-String zu übergeben.

Das ganze wird dadurch deutlich weniger fehleranfällig, übersichtlicher und vor allem sicherer, vor allem gegen SQL-Injection Angriffe. Kodierung von evtl. vorkommenden Anführungszeichen, Hochkommata, u.s.w. übernimmt dieser Weg genauso wie die richtige Übergabe von Datumswerten, u.s.w.

Beispiel (für OleDb)


com.CommandText = "INSERT INTO sources (name, klasse, parameter) VALUES(?, ?, ?)"; 
com.Parameters.Add("name", OleDbType.VarChar).Value = wertName;
com.Parameters.Add("klasse", OleDbType.VarChar).Value = wertKlasse;
com.Parameters.Add("parameter", OleDbType.VarChar).Value = wertParameter;

Die Reihenfolge muß dabei der Reihenfolge der Fragezeichen (?) innerhalb des CommandTextes entspechen. Der Typ muß evtl. angepasst werden.

Etwas einfacher ist es unter .NET 2.0, da kann man das ganze auf folgendes reduzieren:


com.CommandText = "INSERT INTO sources (name, klasse, parameter) VALUES(?, ?, ?)"; 
com.Parameters.AddWithValue("name", wertName);
com.Parameters.AddWithValue("klasse", wertKlasse);
com.Parameters.AddWithValue("parameter", wertParameter);

-Martin Ehrlich

06.03.2006 - 19:54 Uhr

Guten Abend.

Eigentlich ist es keine sehr schwierige Sache. Es hängt ein wenig davon ab, wieviel Funktionalität man von diesen "Business"-Klassen erwartet. Will man tatsächlich die volle Funktionaltität in Business-Klassen haben, die auch das DataSet/DataTable anbietet, kommt man nicht darum eine Reihe von Schnittstellen, vor allem aus dem System.ComponentModel Namensraum zu implementieren.

Namentlich fällt mir da ein:

Für die Collection:
IBindingList für die Collection (verwendet intern IList, ICollection, IEnumerable, u.s.w.)
Aber hier gibt es ab .NET 2.0 zum Glück die generische Version mit BindingList<T>. Ermöglicht z.B. AddNew(), bei dem mit anschließendem CancelEdit(), ein Objekt nicht in die Liste aufgenommen wird; Sortierung (z.B. im GridView), u.a.

Für die Datenobjekte:
IPropertyChange (.NET 2.0)
Damit kann ein Objekt bekanntgeben, wenn sich eines seiner Eigenschaften geändert hat.

IEditableObject
Ermöglicht transaktionelles Bearbeiten von Objekten mit BeginEdit(), EndEdit(), CancelEdit().

ICloneable bzw. ICloneable<T>
Ermöglicht, das ein Objekt von sich selbst eine 1:1 Kopie erstellen kann (evtl. auch als DeepClone).

IComparable bzw IComparable<T>
Damit sich ein Objekt mit sich selbst vergleichen kann. Evtl. weitere IComparer bzw. IComparer<T> für angepasste Vergleichsroutinen (z.B. Sortierung nach mehreren Eiegenschaften).

und und und

Die Liste möglichlicher Schnittstellen hier ist lang. Und DataSet/DataTable und darin verwendet Klassen haben sehr viele davon implementiert.

Aber meistens ist soviel Aufwand bzw. Funktionalität garnicht notwendig. Relationen in Objekten kann man sehr leicht als Properties umsetzen. Und dies läßt sich sogar excellent für DataBinding- und Master/Details-Szenarien einsetzen und das meist deutlich schneller, weil der ganze Oberhead wegfällt, den DataSet/DataTable und Co. eben mitbringen.

Beispiel:

Es geht um ein Bestellsystem, man unterscheidet Produkte, Aufträge, Positionen, ich habe hier der Gewohnheit wegen die englischen Begriffe benutzt.

Zunächst die Klasse für Produkte, bestehend aus einer Id, Name, Beschreibung und Einzelpreis pro Mengeneinheit.


public class Product
{    
    public Guid ProductId
    {
	    get { return _ProductId;}
	    set { _ProductId = value;}
    }
    private Guid _ProductId;

    public string Name
    {
	    get { return _Name;}
	    set { _Name = value;}
    }
    private string _Name;

    public string Description
    {
	    get { return _Description;}
	    set { _Description = value;}
    }
    private string _Description;

    public Decimal Price
    {
        get { return _Price; }
        set { _Price = value; }
    }
    private Decimal _Price;
}

Dann die Klasse, die eine Position darstellt, für die eigentlich nur wichtig ist, welches Produkt und wieviel davon benötigt wird.


public class OrderDetail
{
    public Guid ProductId
    {
	    get { return _ProductId;}
	    set { _ProductId = value;}
    }
    private Guid _ProductId;

    public Product Product
    {
	    get
        {
            // Lazy Initialization: Erst abrufen, wenn benötigt.
            if (_Product == null)
            {
                _Product = ProductProvider.GetProductById(_ProductId);
            }
            return _Product;
        }
    }
    private Product _Product;

    public int Amount
    {
	    get { return _Amount;}
	    set { _Amount = value;}
    }
    private int _Amount;

    public decimal TotalPrice
    {
        get
        {
            return Product.Price * Amount;
        }
    }
}

Besonderheit hier ist, die Child/Parent Relation zum Produkt hin. Eine Datenzugriffsklasse, die z.B. mit einem Webservice oder einer Datenbank spricht stellt diese Klasse zur Verfügung.

Und dann die Klasse Order, die den Auftrag darstellt. Sie erhält eine Id, ein Datum, wann Sie erstellt wurde und die Auflistung der Positionen (Parent/Child Relation). In diesem Fall sind es Listen, die zurück gegeben werden. Die Listen werden ebenfalls von einer Datenzugriffskomponenten bereitgestellt (OrderProvider).


public class Order
{
    public Guid OrderId
    {
        get { return _OrderId; }
        set { _OrderId = value; }
    }
    private Guid _OrderId;

    public DateTime OrderedDateTimeUtc
    {
        get { return _OrderedDateTimeUtc; }
        set { _OrderedDateTimeUtc = value; }
    }
    private DateTime _OrderedDateTimeUtc;

    public List<OrderDetail> Details
    {
        get
        {
            // Lazy Initialization: Erst abrufen, wenn benötigt.
            if (_Details == null)
            {
                _Details = OrderProvider.GetDetailsForOrderId(_OrderId); 
            }
            return _Details;
        }
    }
    private List<OrderDetail> _Details;
}

Und zum Schluß ein paar Stümpfe für die zwei erwähnten Datenzugriffskomponenten.


public class ProductProvider
{
    public static Product GetProductById(Guid productId) { /* ... */ }
}

public class OrderProvider
{
    public static Order GetOrderById(Guid id) { /* ... */ }
    public static void CreateOrder(Order value) { /* ... */ }
    public static void UpdateOrder(Order value) { /* ... */ }
    public static List<OrderDetail> GetDetailsForOrderId(Guid orderId) { /* ... */ }
}

Man man z.B. in einer GridView, DataGrid, ListBox, Repeater oder sowas die Positionen anzeigen will, braucht man nur die Order abrufen und übergibt als DataSource die Liste:


Guid orderId = new Guid("3EEBA609-F90B-4d37-8F82-F3F86FFA75A5");
Order orderItem = OrderProvider.GetOrderById(orderId);
DetailsGridView.DataSource = orderItem.Details;
DetailsGridView.DataBind();

Das ganze ist natürlich nicht vollständig, so fehlt noch der Customer (Kunde), Lieferadresse, Rechnungsadresse und sowas, aber das ließe sich weiter ausbauen. Auch könnte man statt der Listen eigene Collection-Klassen verwenden, z.B.

public class OrderDetailCollection : System.Collections.ObjectModel.Collection<OrderDetail>
{

}

Alternativ könnte man auch von CollectionBase erben, wenn man unter .NET 1.x arbeitet. Diese Klassen könnte man um eigene Methoden erweitern, z.B. einer Methode, die den Gesamtpreis errechnet.


public class OrderDetailCollection : System.Collections.ObjectModel.Collection<OrderDetail>
{
    public decimal CalculateTotalPrice()
    {
        decimal sum = 0;
        foreach (OrderDetail detail in Items)
        {
            sum += detail.TotalPrice;
        }
        return sum;
    }
}

Die Möglichkeiten sind praktisch unbegrenzt je nachdem wieviel man von den Business-Klassen erwartet.

06.03.2006 - 18:55 Uhr

Guten Abend.

Ich hatte kürzlich dasselbe Problem, und es ist einfach zu lösen, auch wenn man es nicht vermuten würde. Leider ist die Managed API für die Office-Produkte eine der schlechteren Umsetzungen und ich ersehen den Tag, an dem Microsoft sich entschließt diese neu aufzusetzen, oder gleich ein managed Office implementiert. Angesichts der neue Produktlinie Visual Studio Tools for Office 2003/2005 hatte ich schon Hoffnung, die sich aber nicht bestätigt hat.

Aber zur Sache:

Auch wenn Intellisense und der Objekt-Explorer etwas anderes sagen, verwendet man folgendes Konstrukt erhält man tatsächlich die Spalte:


Excel.Range oRange = (Excel.Range)ws.Columns[SpaltenNummer, Type.Missing]; 

Auch wenn es zunächst so aussieht, als würde man damit die Reihe abfragen.

-Martin Ehrlich

04.03.2006 - 13:19 Uhr

Guten Tag.

Ich bin mir zwar nicht ganz sicher, aber wenn ich ein Add-In schreibe, erteile ich das Guid-Attribut direkt der Klasse, die "IDTExtensibility2" implementiert. Denn Office läst über COM/Registry zunächst den Klassennamen suchen (in Ihrem Fall "ExcelAddTest.ExcelAdd", sucht darin die CLSID der Klasse (die Guid) und läßt sich dann von der mscoree.dll eine Instanz der Klasse mit dieser CLSID geben, genauer gesagt den CCW, mit dem Office dann "spricht". In Ihrem Beispiel ist jedoch nur der Assembly eine Guid zugewiesen. Aber wie gesagt, in diesem Punkt bin ich mir nicht 100% sicher.

Es könnte noch einen zweiten Grund geben:

Haben Sie Visual Studio 2003 und 2005 auf dem selben Rechner passen Sie auf welche Version Sie für ihr Add-In verwenden (.NET 1.1 oder 2.0). Es gibt für beide Versionen ein eigenes RegAsm und ich habe schon Probleme festgestellt, wenn man die falsche verwendet.

Noch ein Tip, tragen Sie in der AssemblyInfo besser immer folgendes ein:

[assembly: ComVisible(false)]

Und geben Sie in der Klasse die "IDTExtensibility2" implementiert explizit immer:


[Guid(<IhreGuid>)]
[ComVisible(true)]
public class ExcelAdd : Extensibility.IDTExtensibility2
{
   // ...
}

Aus zwei Gründen, erstens ist diese Klasse sowieso die einzige mit der Office sprechen will und zweitens wenn Sie ComVisible(true) in der AssemblyInfo belassen, haben Sie Ihre wahre Freude daran jede Ihrer (Hilfs-)Klassen in der Registry verewigt zu sehen.

Dann gilt es als eine Art ungeschriebene Regel jeder Com-Callable Klasse eine eigene ProgId zu erteilen. Im Office Add-In Bereich hat sich hier "AddInName.Connect" etabliert. Damit wird sofort klar, das es diese Klasse ist, mit der Office sich verbinden soll. Es ist allerdings keine vorrausssetzung, wichtig ist nur, das der Registry-Schlüssel im Office-Zweig den jeweiligen Namen der Klasse zurückgibt (oder eben die ProgId).


[Guid(<IhreGuid>)]
[ComVisible(true)]
[ProgId("<IhrAddIn>.Connect")]
public class ExcelAdd : Extensibility.IDTExtensibility2
{
   // ...
}

Übrigens könnten Ihnen noch weitere Stolpersteine im Weg liegen, bis Ihr Add-In wirklich von Excel geladen wird, dazu gehören vor allen noch einige Registry-Schlüssel im Office-Zweig.

-Martin Ehrlich

01.02.2006 - 22:52 Uhr

Guten Abend.

Natürlich können String-Variablen Null sein. Eine Möglichkeit Leer-Strings und Null-Strings gleichzeitig abzufackeln ist folgende Variante:


lbEingebucht.Text = string.IsNullOrEmpty(daten.Eingebucht) ? "---" :  daten.Eingebucht ; 

-Martin Ehrlich

01.02.2006 - 22:44 Uhr

Guten Abend.

Ich habe mich mal länger damit auseinander setzen müssen. Ich vermute es geht darum zur Laufzeit den String zu übergeben, das dem Template zugrunde liegen soll. So in der Form

// Pseudocode
Repeater.ItemTemplate = "<b><%# Eval(&quot;Name&quot;) %></b>";

Variante 1:
Es ist mir mal gelungen dies mit Ausdrücken hinzukriegen, die keine DataBinding-Expressions enthielten. Aber das nützt einem recht wenig, gerade im Repeater. Die Idee war in etwa eine Klasse vom Typ TemplatedControl erben zu lassen, darin ist eine Methode, die aus einem String den Parser dazu bringt die Objekthierarchie abzuleiten (was man in CreateChildControls() macht). Den String konnte man z.B. aus einer Datenbank abfragen. Leider geht das nicht mit DataBinding-Expressions.

Ich habe mal mit Microsoft drüber gesprochen. Die Entwickler sagten, sie fänden die Idee interessant, aber können es für die aktuelle Version (ASP.NET 2.0) nicht mehr umsetzen.

Variante 2:
Aber es gibt einen Weg über das Interface ITemplate, das der Repeater hier haben will. Normalerweise wird die Klasse ITemplate "OnTheFly" implementiert aus dem Code den man in der ASPX oder ASCX Seite geschrieben hat.

An dieser Stelle wird ein Stück Text in eine Objekthierarchie umgewandelt (durch den Parser von ASP.NET). Von daher hatte ich auch die Idee zu Variante 1.

Nun steht einem nichts im Wege, das Objektmodell einfach selbst zusammenzubauen wenn der Repeater einen dazu anweist. Leider ist diese Angelegenheit vor allem mit DataBinding-Expressions extrem unangenehm (daher hatte ich mit Microsoft auch Kontakt aufgenommen).

Bleibt Variante 3: (die einzig praktikable)
Man kann ITemplate Objekte aus ASCX Usercontrols generieren lassen.


MyRepeater.ItemTemplate = Page.LoadTemplate("MyTemplate.ascx");

Sehr Abenteuerliche könnten jetzt auf die Idee kommen: Dann lege ich die ASCX Datei doch einfach zur Runtime in einem temporären Verzeichnis mit System.IO an und übermittle danach den Pfad. Ich habe es nicht ausprobiert, aber es sollte funktioniert.

An dieser Stelle hat mir der Microsoft Entwickler noch den Tipp gegeben einen VirtualPathProvider zu implementieren. Das ist eine Schnittstelle, mit der man ASP.NET dazu kriegen kann sich den Code für ASPX und CS/VB Codebehinddateien, Web.configs, u.s.w. nicht aus dem Dateisystem, sondern von irgendwo anders her zu holen (z.B. auch einfach aus einem String im Speicher). Die Idee ist nicht schlecht, aber irgendwie mit "Kanonen auf Spatzen geschossen".

Fazit:
Obwohl Variante 2 und Variante 3 beide Strings aus dem Dateisystem durch den ASP.NET Parser jagen und prima funktionieren ist es leider vergessen worden von Microsoft einen Weg einzubauen, Page.LoadTemplate() für einfache Strings bereitzustellen.

-Martin Ehrlich

01.02.2006 - 22:15 Uhr

Guten Abend.

Es gibt mittlerweile zwei (in .NET 2.0).

System.Web.Mail (.NET 1.x und 2.0)
System.Net.Mail (nur .NET 2.0)

Das Beispiel mit dem Code bezog sich auf letzteres (System.Web.Mail). Und da es sich im Namespace System.Web.Mail befindet, sollte man in Windows-, Konsolenanwendungen und Bibliotheken die Assembly "System.Web.dll" hinzufügen (über "Add Reference ...").

Zur Info:
System.Web.Mail verwendet intern die CDO Schnittstelle von Windows (via COM). System.Net.Mail ist eine Neuentwicklung in Managed Code und ich kann eigentlich nur jedem empfehlen diese zu verwenden soweit möglich, denn sie ist deutlich flexibler als die Implementierung in "System.Web.Mail.".

-Martin Ehrlich

09.01.2006 - 20:08 Uhr

Guten Abend.

Einfach ist meist am Besten, wie wäre es mit:


Console.WriteLine(DateTime.Now.ToString("R"));

Entspricht dem DateTimeFormatInfo.CurrentInfo.RFC1123Pattern.

-Martin Ehrlich

05.01.2006 - 11:28 Uhr

Guten Tag.

Probieren Sie mal:

DataRow[] maxRow = dataTable.Select("MAX(<Spalte>)");
if (maxRow.Length > 0)
Console.WriteLine((decimal)maxRow[0][0]);

Ich habe es nicht getestet, aber es müßte funktionieren.

-Martin Ehrlich

08.12.2005 - 21:09 Uhr

Guten Abend.

Ich vermute es liegt nicht an einem Virenscanner oder ähnlichem, denn dann würde es vermutlich eher eine Win32 Exception oder Unmanaged Exception oder ähnliches geben.

Wird das Programm von der lokalen Platte oder über das Netzwerk/Freigabe aufgerufen (auch wenn die zum selben Rechner zeigt)?

Bei letzterem, erhält die Anwendung einen anderen Berechtigungssatz (entweder Intranet oder sogar Internet) und da beginnen ohne zusätzliche Konfiguration einige Einschränkungen in der UI zu greifen.

Ist dies der Fall müssen zusätzliche Konfigurationen für die Code Access Security getroffen werden "Einstellungen" > "Verwaltung" > ".NET Konfiguration".

-Martin Ehrlich

08.12.2005 - 21:00 Uhr

Guten Abend.

Was die Dns Problematik angeht, gibt es einige in .NET geschriebene verfügbare Komponenten, die alles zum Thema Dns anbieten können. Ich glaube da waren auch ein paar Freeware-Komponenten dabei.

Ansonsten selbst schreiben. Sind eigentlich nur ein paar UDP-Requests, die man senden und auswerten muß.

Ansonsten kann man vielleicht auch einfach Nslookup verwenden (aufrufen mit Process und Umleiten der Ausgabe) und dessen Ausgaben irgendwie verwerten und verwenden.

Was die Subdomains angeht, hierfür ist meines erachtens nach die Berechtigung notwendig sich die Zone komplett herunterladen zu dürfen. Dieses Recht wird jedoch bei den meisten Domains nur anderen Dns-Servern, die als Mirror oder Cache dieser Zone herhalten gewährt.

-Martin Ehrlich

01.12.2005 - 00:01 Uhr

Guten Abend.

Die Sache mit dem "Warum wird mein Fenster geschlossen" wird mit .NET 2.0 gelöst. Da verfügt das FormClosing-Event nämlich über einen geänderten Parameter.


private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
   // e.CloseReason, z.B: CloseReason.TaskManagerClosing;
}

In dem CloseReason sind so ziemlich alle Möglichkeiten enthalten.

Bei Interesse aber fehlendem Visual Studio 2005 empfehle ich sich einfach mal die Visual C# Express Edition runterzuladen und zu registieren (oder umgekehrt). Das Produkt ist Final und Microsoft wird es ein Jahr lang gratis anbieten, danach wohl 60$ oder so kosten. .NET 2.0 ist ja bereits freigegeben, es spricht also nichts mehr dagegen .NET 2.0 Anwendungen zu schreiben.

-Martin Ehrlich

30.11.2005 - 23:48 Uhr

Guten Abend.

Ich habe mir die Sache doch nochmal näher angesehen. Die XML Kommentare lassen sich extrahieren in eine XML-Datei, das ist klar. Fest steht außerdem, daß sie nicht in die Assembly (Dll) geschrieben werden.

Wichtig für Visual Studio ist einzig und allein, daß sich im selben Verzeichnis in der auch das Assembly liegt sich die XML-Datei befindet. Und wichtig ist außerdem das sie denselben Namen trägt wie das Assembly ohne die Dateiendung.

Also:

MyAssembly.dll
MyAssembly.xml

Bekommt sie einen anderen Namen, erkennt Visual Studio die Datei nicht mehr und es gibt kein Intellisense.

So macht Microsoft das übrigens:

Die wirkliche DLL, z.B. System.Web.DLL liegt im GAC im %WINDIR%\Assembly Verzeichnis. Aber was man über "Verweis hinzufügen ..." hinzufügt verweist nach:

%WINDIR%\Microsoft.NET\Framework\v1.1.4322

Und darin ist ein Verzeichnis "DE" und darin ist - oh wunder - eine Datei namens "System.Web.Xml"

Schade eigentlich. Denn es fehlt nun noch ein Weg die DLL dazu zu bringen die Kommentare mit aufzunehmen, entweder als Ressource (habe ich versucht, geht nicht) oder direkt im IL-Code.

Wenn ich als Komponenten-Entwickler meine DLL gerne kommentiert rausgeben möchte, muß ich immer beide Dateien mitliefern. Und da ich in den GAC nur DLL's legen kann, heißt das ich muß ein Runtime-Setup bauen, daß die DLL nur in GAC legt und ein SDK-Setup das:

1.) Die DLL in den GAC legt.
2.) Ein zusätzliches Verzeichnis anlegt.
3.) Darin die DLL und die XML-Datei legt

und im Optimalfall:
4.) Einen Registry-Key unter:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\AssemblyFolders&lt;xyz>
(für VS2003)
hinterlegt mit Standardwert, der auf das Verzeichnis mit dem Assembly verweist.

Dann erscheint meine Assembly nämlich auch im Register ".NET" bei "Verweis hinzufügen" und ich kann mir das ständige raussuchen sparen.

-Martin Ehrlich

30.11.2005 - 20:17 Uhr

Guten Abend.

Das scheint mir ein Fehler zu sein. Die ganzen Inline-Kommentare wie z.B. summary werden in das Manifest der Assembly mit aufgenommen und müßten daher im Intellisense nach einbinden der DLL wieder zu sehen sein.

Prüfen Sie doch nochmal ob es sich um die richtige DLL handelt und die Summaries an der richtigen Stelle sind. Prüfen Sie auch, daß es keine Namensüberlagerungen gibt, die können das Intellisense auch durcheinander bringen. z.B. Wenn Klassen wie Namensräme benannt sind. Oder zwei Namensräume mit gleichen Unternamensräumen vorhanden sind.

Im Intellisense sehen sie meist ein rotes Symbol davor wenn es Probleme damit gibt.

-Martin Ehrlich

30.11.2005 - 20:10 Uhr

Guten Abend.

AppDomains können nicht direkt miteinander kommunizieren (gemeinsamer Speicherbereich), das heißt Sie brauchen einen Kommunikationsverbindung zwischen den beiden um Informationen von a nach b zu bringen. z.B. mit Remoting oder TCP/IP Sockets direkt.

Die Idee wäre es nun, Sie schreiben sich einen eigenen TraceListener, der ihre TraceAusgaben an eine zentrale Stelle oder die andere AppDomain sendet (z.B. die Hostanwendung) via Sockets oder Remoting.

Vorteil dieser Lösung:
Das jeweilige Plugin braucht nichts davon zu wissen. Es schreibt einfach weiter seine Trace-Ausgaben in Debug.Write... bzw. Trace.Write... Der eigene TraceListener sendet diese Daten dann transparent an die Host-Anwendung, die dann z.B. ihrerseits die Trace-Ausgaben entgegennehmen kann und z.B. in das eigene Trace schreibt.

Alternativ könnte man die Daten auch in einen Datenbank schreiben auf die beide AppDomains gleichzeitig zugreifen.

-Martin Ehrlich

26.11.2005 - 16:23 Uhr

Guten Tag.

Wie bereits schon erwähnt steht die erste Zahl in den geschweiften Klammer immer für den Wert an n-ten Stelle in der Liste der Argumente.

Nach dem Komma kann man nun den String eine feste Länge geben. Positive Werte richten den String rechtsbündig aus, negative linksbündig. Diese Stringausrichtung ist vor allen in der tabellarischen Ausgabe von Daten in ASCII-Dateien oder der Console sinnvoll.

Nach dem Doppelpunkt kommt dann die Angabe, wie man den entsprechenden Wert ausgeben möchte. Hier gibt es Standard-Formatangaben für Datums-/Zeitwerte (z.B. N2 für Zahlen mit Tausender-Trennpunkt und immer 2 Nachkommastellen inkl. Rundung) und natürlich kann man auch eigene Formate verwenden, wie z.B. das "yyyMMdd". Was jedoch meiner Meinung nach falsch ist. Denn es gibt ein "y" für eine 1- bis 2-stellig Jahresausgabe, also 1 für 2001, 2 für 2002, aber 11 für 2011 und die immer 2-stellig ist "yy" und eine die immer 4-stellig ist. Aber "yyy" ist in der Liste eigentlich garnicht drin.

Das ",1" macht eigentlich auch keinen Sinn. Denn er bedeutet, wenn der String jemals 0 Zeichen lang sein sollte, dann wird statt dem Leerstring "" ein einzelnes Leerzeichen " " zurückgegeben.

Meiner Meinung nach müßte es daher wie folgt aussehen:


String.Format("{0:yyyyMMdd}", Convert.ToDateTime("2005-11-25 13:18:00.000")) 

oder gleich:


Convert.ToDateTime("2005-11-25 13:18:00.000").ToString("yyyyMMdd") 

-Martin Ehrlich

15.11.2005 - 00:28 Uhr

Guten Abend.

Zu diesem Thema habe ich bereits mal eine Antwort geschrieben, die vielleicht weiterhilft. Es stimmt nämlich nicht, daß .NET keine Klassen für komplexere Datums-Differenzmethoden kennt. Sie sind nur irgendwie etwas unglücklich abgelegt (und schwer zu finden).

DateTime problem

-Martin Ehrlich

11.11.2005 - 21:50 Uhr

Guten Abend.

Irgendwie erscheint mir das ursprüngliche Problem mit enums nicht adäquat gelöst. Es scheint sich ja um eine Zuorndung von Strings zu Ganzzahlen zu handeln, daher würde ich eine Hashtable bzw. ab .NET 2.0 die generische Klasse Dictionary<string,int> verwenden.

Aber vielleicht sind auch ja die sog. Flags gewünscht hierzu ein Beispiel:


static void Main(string[] args)
{
	Test s = Test.WertA;
	Console.WriteLine(s); // Ergibt: WertA

	s = Test.WertC | Test.WertF;
	Console.WriteLine(s); // Ergibt: WertC, WertF

	s = Test.SonderWert1;
	Console.WriteLine(s); // Ergibt: SonderWert1

	Console.ReadLine();
}

[Flags]
enum Test
{
	WertA = 0x1,
	WertB = 0x2,
	WertC = 0x4,
	WertD = 0x8,
	WertE = 0x10,
	WertF = 0x20,
	WertG = 0x40,
	WertH = 0x80,
	SonderWert1 = WertA | WertH,
	SonderWert2 = WertA | WertC | WertB
}

Das nächste mal vielleicht einfach etwas genauer schildern was erreicht werden soll.

-Martin Ehrlich

25.10.2005 - 17:24 Uhr

Guten Abend.

Alternativ steht auch folgende Möglichkeit zur Verfügung.


using System.Reflection;
...
Console.WriteLine(Assembly.GetExecutingAssembly().Location);

Dies geht dann ohne das Schwergewicht System.Windows.Forms.Dll beim Starten der Anwendung mit zu laden.

-Martin Ehrlich

08.10.2005 - 15:31 Uhr

Guten Tag.

Für gewöhnlich ist ja TimeSpan für Zeitabstände zuständig. Leider hört der TimeSpan bei Days auf.


DateTime d1 = DateTime.Now; // 8.10.
DateTime d2 = d1.AddDays(24); // 1.11.
Console.WriteLine(d2.Subtract(d1).TotalDays); // Ergibt 24

Für kleinere Zeitspann ist aus das ausreichend. Problematisch ist es mit größeren Zeitabständen zu rechnen, z.B. Wochen, Monaten, Jahren, u.s.w. vor allem, wenn sie sich an den Kalender halten sollen.

Es fehlt eigentlich noch sowas wie eine DateSpan, aber das hat es noch nicht man in .NET 2.0 geschafft. Aber es gibt Abhilfe: Früher gab es da unter Visual Basic eine schöne Methode namens DateDiff, die sowas konnte. Blätter man nun etwas so findet man irgendwann folgende Möglichkeit, dazu muß jedoch zunächst die DLL "Microsoft.VisualBasic" eingebunden werden und das using Statement. Dann sieht das ganze wie folgt aus:


DateTime d1 = DateTime.Now; // 8.10.
DateTime d2 = d1.AddDays(24); // 1.11.
DateTime d3 = d1.AddDays(23); // 31.10.
Console.WriteLine(DateAndTime.DateDiff(DateInterval.Month, d1, d2, FirstDayOfWeek.System, FirstWeekOfYear.System)); // Ergibt 1
Console.WriteLine(DateAndTime.DateDiff(DateInterval.Month, d1, d3, FirstDayOfWeek.System, FirstWeekOfYear.System)); // Ergibt 0

-Martin Ehrlich

06.10.2005 - 17:09 Uhr

Guten Tag.

Probieren Sie mal folgendes:


string variable = "Test";
Console.WriteLine(String.Format("{0,-10}", variable));

Dies füllt den String auf 10 Zeichen auf und richtet Ihn rechtsbündig aus. 10 statt -10 richtet das ganze Linksbündig aus. Kann auch sein, das es umgekehrt war, ich komme da immer durcheinander.

-Martin Ehrlich

06.10.2005 - 13:59 Uhr

Guten Tag.

Es ist Match.Value.

-Martin Ehrlich

04.10.2005 - 19:09 Uhr

Guten Abend.

Eine recht übersichtliche Methode zu prüfen ob eine Variable von einem bestimmten Typ ist unter Berücksichtigung evtl. Vererbungen ist der is-Operator. Beispiel:


class Program
{
	static void Main(string[] args)
	{
		Class1 c1 = new Class1();
		Class2 c2 = new Class2();

		CheckClass("c1", c1);
		CheckClass("c2", c2);
		Console.ReadLine();
	}

	static void CheckClass(string name, object o)
	{
		if (o is Class1)
			Console.WriteLine(name + " ist eine Class1.");
		if (o is Class2)
			Console.WriteLine(name + " ist eine Class2.");
	}
}

class Class1
{

}

class Class2: Class1
{

}

Ergebnis:


c1 ist eine Class1.
c2 ist eine Class1.
c2 ist eine Class2.

Bei der Prüfung muß man dann natürlich aufpassen, daß wie in diesem Fall c2 beides ist, daher sollte man in einer if ... else if ... else if ... else ... Prüfung die jeweils spezifischere Klasse früher prüfen.

Dieser Weg ist deutlich anwendungsfreundlicher als mit Variable.GetType().Name oder .FullName oder gar den Reflection Methoden zu arbeiten (wenn gleich intern sicherlich ähnliches verwendet wird, aber ich denke die Microsoft Implementierung ist der eigenen vorzuziehen und in der einen oder anderen Weise optimiert).

-Martin Ehrlich

01.10.2005 - 19:45 Uhr

Guten Abend.

Die ListBox weigert sich dasselbe ListItem-Objekt zweimal in der List zu haben was kurzzeitig der Fall ist, ich empfehle Ihnen statt dessen den zu verschiebenden Eintrag aus der Liste rauszuholen mit Remove und dann mit Insert wieder einzufügen.

Wenn Sie vor der Operation ListView.BeginUpdate() und abschließend ListView.EndUpdate() aufrufen ist die Operation perfekt, denn solange wird die UI nicht aktualisiert.

Außerdem sollten Sie aufpassen, das Sie nicht am oberen Rand nach oben bzw. am unteren nach unten verschieben, denn dann kriegen Sie eine OutOfRangeException.

Mit dem Selected müssen Sie beim Remove/RemoveAt, Insert auch nichts machen, denn die Info ist am ListItem vermerkt und beim Einfügen wieder da.

-Martin Ehrlich

01.10.2005 - 13:06 Uhr

Guten Tag.

Eine etwas abgefahrene Möglichkeit ist es den Code dynamisch zur Laufzeit zu generieren und anschließend zu instanzieren. Ein Ausgangspunkt für Informationen ist hier:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpcongeneratingcompilingsourcecodedynamicallyinmultiplelanguages.asp

Die Idee wäre es nun zur Laufzeit eine Klasse zu erstellen, die von der Klasse die erweitert werden soll erbt. Der Code wird dann intern compiliert und anschließend kann man ihn dann via Reflection instanzieren. ASP.NET nutzt diese Funktion recht intensiv.

Meines erachtens nach wäre es dennoch besser mit Collections/Generics bzw. Arrays zu arbeiten. Was Ihnen vorschwebt ist sicherlich sowas wie das Item/Fields Modell aus z.B. Sharepoint, Exchange oder anderen Messaging Anwendungen. Wenn Sie unbedingt Properties brauchen klingt das ein wenig nach Ärger mit DataBinding oder Ärger mit Webservices unter 1.x, die beide Properties brauchen. Aber zumindest für ersteres gibt es im System.ComponentModel Namespace eine ganze Reihe von Interfaces.

-Martin Ehrlich

30.09.2005 - 13:33 Uhr

Guten Tag.

Versuchen Sie es mal mit:


using System.Reflection;
...
Console.WriteLine(Assembly.GetExecutingAssembly().Location);

-Martin Ehrlich

30.09.2005 - 13:30 Uhr

Guten Tag.

Eine nette Implementierung für CAPI Zugriffe unter .NET gibt es hier:
http://www.freeware.de/Windows/Programmierung/Programmierung/Active_X_DLL/Detail_AKA_ISDN_Library_15655.html

Eine andere Firma arbeitet ebenfalls an einer Umsetzung der CAPI für Managed Code, jedoch kommerziell:
http://capi-sdk.ksware.de/

Mit C# eine Anwendung zu basteln, die Rufnummern beim Eingehen identifiert, u.s.w. ist damit relativ schnell zu bewerkstelligen sogar für Leute, die sich mit den C/C++ orientierten Schnittstellen bzw. der CAPI selbst nicht auskennen.

-Martin Ehrlich

30.09.2005 - 13:20 Uhr

Guten Tag.

Es ist manchmal etwas mit Kanonen auf Spatzen geschossen immergleich die Regex-Keule hervorzuholen für sehr einfache Prüfungen.

Wenn Sie einzelnen Zeichen prüfen wollen, bieten sich die statischen Methoden der Klasse char an.


char c = '1';
Console.WriteLine(char.IsDigit(c)); // true
Console.WriteLine(char.IsLetter(c)); // false
c = 'A';
Console.WriteLine(char.IsDigit(c));  // false
Console.WriteLine(char.IsLetter(c)); // true

Alternativ bietet sich ab .NET 2.0 eine sehr elegante Möglichkeit Zeichenketten zu prüfen und gleichzeitig umzuwandeln, das sogenannte TryParse-Pattern.


string variable = "A";
int wert;

if (int.TryParse(variable, out wert) == true)
{
	Console.WriteLine(String.Format("War eine Zahl, nämlich: {0:N0}", wert));
}
else
{
	Console.WriteLine("Leider keine Zahl.");
}

Dieses Pattern ist für die meisten Datentypen vorhanden. Leider haben Sie es bei System.Guid vergessen, da muß man sich dann anders helfen.

-Martin Ehrlich

29.09.2005 - 20:54 Uhr

Probieren Sie doch mal die Eigenschaft MultiSelect auf false zu setzen:

ListView.MultiSelect = false;

Oder einfach den alten Eintrag auf false zu setzen.

ListView.Items[n].Selected = false;

Klein Tip noch, wenn Sie HideSelection auf false setzen, können Sie sich das zurücksetzen des Focus sparen. Sie sehen auch dann welche Einträge selektiert sind, wenn das ListView nicht den Focus hat.

-Martin Ehrlich

29.04.2005 - 20:30 Uhr

Hallo.

Sehr einfach zu verwenden für die Wiedergabe von Audio- und Video-Material ist Managed DirectX, die Erweiterung für DirectX seit DirectX 8. Wer die DirectX Redistributional schon mal installiert hat (wer hat das eigentlich nicht), dem ist vielleicht am Ende das kleine Fenster mit "Managed blabla" aufgefallen.

Um damit zu entwickeln lädt man sich am besten das DirectX SDK (aktuelle Version Feburar 05 Update) herunter. Danach kann man in seine Projekte die "DirectX.AudioVideoPlayback.dll" einbinden. Danach ist die Sache einfach.


using Microsoft.DirectX.AudioVideoPlayback;

...

string filename = "Song.mp3";
Audio audioPlayback = new Audio(filename);
audioPlayback.Play();

...

Die Klasse Audio bietet noch viele weitere Eigenschaften und Methoden an das Playback zu steuern, z.B. Position, Lautstärke, u.s.w.
Im Hintergrund verwendet DirectX Codecs, die auf dem Rechner installiert sind. Daher wird das Playback auch für andere Formate wie WAV, WMA, u.s.w. funktionieren.

Video-Playback ist übrigens ähnlich einfach. Dies regelt (wie sollte es anders sein) natürlich die Klasse "Video". Viel Spaß beim Probieren.

-Martin Ehrlich

29.04.2005 - 12:16 Uhr

Hallo.

Schau doch mal in den Ordner:
%PROGRAMFILES%\Microsoft Visual Studio .NET 2003\Common7\Graphics

Bzw. in VS2005:
%PROGRAMFILES%\Microsoft Visual Studio 8\Common7\VS2005ImageLibrary

Diese Icons wurden mit den entsprechenden Visual Studio Versionen mitgeliefert und sind - gültige Lizenz vorausgesetzt - dafür gedacht in eigenen Anwendungen Verwendung zu finden. Vor allem letztere sind sehr schön in HighColor vorhanden und entsprechend praktisch allen Icons, die auch in der VS2005 IDE enthalten sind.

-Martin Ehrlich

29.04.2005 - 12:12 Uhr

Hallo.

Schau doch mal in den Ordner:
%PROGRAMFILES%\Microsoft Visual Studio .NET 2003\Common7\Graphics

Bzw. in VS2005:
%PROGRAMFILES%\Microsoft Visual Studio 8\Common7\VS2005ImageLibrary

Diese Icons wurden mit den entsprechenden Visual Studio Versionen mitgeliefert und sind - gültige Lizenz vorausgesetzt - dafür gedacht in eigenen Anwendungen Verwendung zu finden. Vor allem letztere sind sehr schön in HighColor vorhanden und entsprechend praktisch allen Icons, die auch in der VS2005 IDE enthalten sind.

-Martin Ehrlich

28.04.2005 - 23:06 Uhr

Hallo.

Was dort zu finden ist, ist eine Preview der zwei Longhorn Bausteine "Avalon" und "Indigo". Beide sind Bausteine der neuen Windows-API "WinFX" (Nachfolger von Win32) und sind damit Teil von "Longhorn". Aber eigentlich kann man nicht von DER Longhorn-Anwendung sprechen, aber dazu gleich mehr.

"Avalon" ist der Codename für die neue Grafik API für Longhorn-basierte Anwendungen. Sie soll langfristig ein Ersatz für das heutige GDI+ werden, mit dem heutzutage fast alle Windows-UI auf den Bildschirm gebracht wird (ausgenommen Spiele). "Avalon" wird endlich das gigantische Grafik-Potential heutiger Grafikkarten für Windows-Anwendungen nutzbar machen, das heute allein durch DirectX Anwendungen genutzt wird.

Avalon bringt einige Neuerungen mit, die Grundlagen für die Technik der nächsten 10 Jahre Windows sein wird. So wird jede grafische Operation durch Hardware beschleunigt soweit verfügbar. Darüber hinaus soll damit der Sprung von Pixel-basierter zu Vektor-basierter UI erfolgen, womit stufenlosen Zoomen von Oberflächen möglich wird. Dies öffnet heute schon verfügbaren hochauflösenden Displays den Weg. Denn Pixel-basierte Oberflächen sehen darauf noch microskopisch klein aus.

Die Programmierschnittstelle - die API - wird auf Basis von .NET bereitgestellt werden und neue Konzepte und Möglichkeiten mitbringen. So wird es eine neue deklarative, xml-konforme Sprache namens XAML geben mit der Oberflächen nicht mehr nur programmiert sondern auch deklariert werden können. Dies funktioniert ähnlich wie heute mit HTML/CSS-Dateien im Browser. Damit wird die Trennung von Anwendungsdesign und Code deutlich einfacher und sauberer. Die Verbindung zwischen beiden wird durch Events, "partial classes" bzw. "code behind" und Databinding sichergestellt.

Auch wird es Features geben, die man heute eher in 3D oder Vektor-Programmen findet, denn die 3D Unterstützung für die Visualisierung wird ganz groß geschrieben und es gibt eine Schnittstelle für Vektor- und 3D-Animation, sowie Story-boarding. (Macromedia Flash lässt grüßen) Außerdem wird es Features aus dem PostScript/PDF Bereich geben, die heutzutage eher im Adobe-Bereich zu finden sind, z.B. ein geräteunabhängiges Druckformat.

Wie gesagt, Microsoft will für die nächsten 10 Jahre Windows eine Grafik-Plattform schaffen. Entsprechend groß sind die Anstrengungen, die derzeitig darin investiert werden.

"Avalon" wird übrigens nicht nur für Longhorn verfügbar sein, sondern auch für Windows XP SP2 und Windows 2003 oder höher (das gleiche wird für "Indigo" auch gelten). Derzeitig gibt es eine Preview (Alpha-Version). Im Sommer wird es eine Beta 1 und im Herbst eine Beta 2 geben. Release soll Sommer 2006 sein. Aber Terminierungen sind nicht Microsofts Stärke, da kann sich durchaus noch einiges verschieben.

"Indigo" wird die neue Kommunikations-Plattform der nächsten 10 Jahre Windows bilden und soll ebenfalls eine Reihe von heutigen Technologien zusammenführen oder ersetzen, zu nennen wäre hier Web-Services, COM+/Enterprise Service, Remoting, Message Queuing und die ganzen W3C Technologien, die es heute im WSE gibt. Aber das ist ein ganz eigenes Thema.

Und wo wir gleich dabei sind fehlt noch ein Baustein - meiner Meinung nach der wichtigste und interessanteste - "WinFS". Das neue Dateisystem das NTFS mit Features verbindet, die man eher aus Datenbanken kennt. Dies hat sich als der umstrittenste und schwierigste der drei Bausteine herauskristallisiert. Daher wird dieser Baustein zur Veröffentlichung von Longhorn wohl erst als Beta verfügbar sein.

Schade eigentlich. Die Previews der PDC 2003 waren bereits sehr interessant und würden viele heutige Daten-Speicherproblem in Angriff nehmen. Man darf gespannt sein.

-Martin Ehrlich

26.04.2005 - 21:42 Uhr

Hallo.

Um ganz sicher zu gehen, ob eine Sonderbehandlung bei Zugriffen auf Daten, z.B. Controls eines Formular notwendig ist, gibt es eine Eigenschaft, die finde ich sehr einfach zu verwenden ist und eine klare Auskunft gibt:


public void SetMessage(string message)
{
  if (this.InvokeRequired == true)
  {
    // Zugriff nur mit Invoke()/BeginInvoke()
  }
  else
  {
    // Zugriff ohne Sonderbehandlung möglich
  }
}

Entsprechend angepasst, kann man darin den Invoke/BeginInvoke-Code schön kapseln und von aussen braucht man nurnoch die Methode direkt aufrufen, Sie entscheided dann selbst, ob sie noch ein Invoke benötigt.

Wer keine zusätzlichen Threads (bzw. asychrone Aufrufe), z.B. für langsame Netzwerk-/Datenbank-Zugriffe oder Rechenoperationen verwendet, kann sich das alles natürlich sparen.

-Martin Ehrlich

26.04.2005 - 19:04 Uhr

Hallo.

es gibt ein empfohlenes und bewährtes Anwendungsdesign für solche Fälle. Es erlaubt ein sauberes Anwendungsdesign. Ich selbst habe es unzählige Male eingesetzt und kann es nur empfehlen.

Jedes Formular hat eine Eigenschaft DialogResult, das zurückgegeben wird, wenn man direkt die Funktion ShowDialog() statt Show() verwendet. Ein entsprechender Eventhandler für ein Ereignis, daß den Options-Dialog auslöst.


...
using (FormOption dlg = new FormOption())
{
  if (dlg.ShowDialog() == DialogResult.Ok)
  {
    // Einstellungen übernehmen.
  }
}
...

Der Optionsdialog selbst enthält sicherlich eine "OK" und "Abbrechen" Schaltfläche, deren Eventhändler entsprechend so aussehen.


private void buttonOK_Click(object sender, EventArgs e)
{
  this.DialogResult = DialogResult.OK;
  this.Close();
}

// entsprechend für den Abbrechen-Button

Alternativ kann man auch den Buttons sagen welchen DialogResult sie im Formular festlegen sollen, sobald diese gedrückt werden. Dies ist in der Eigenschaft "DialogResult" eines jeden Button hinterlegt.

Dieses Design ist deshalb vorteilhaft, weil man nicht in die Gefahr gerät die einzelnen Threads der Formulare untereinander synchronisieren zu müssen. Außerdem kann damit die "Lebenszeit" eines Formulars auf das absolut notwendige reduziert werden.

-Martin Ehrlich

22.04.2005 - 17:52 Uhr

Hallo.

Ganz einfach via


public override void Methode()
{
  base.Methode():
}

-Martin Ehrlich

22.04.2005 - 17:49 Uhr

Hallo.

Ich verwendet, seit dem ich mit .NET 2.0 programmiere nurnoch


if (string.IsNullOrEmpty(mystring))
{
  // ... Code
}

Das ist kompakt, eindeutig, selbsterklärend und ich überlasse es Microsoft die schnellste/beste Lösung dafür zu finden.

Ach ja, das ist übriegens:


public static bool IsNullOrEmpty(string value)
{
      if (value != null)
      {
            return (value.Length == 0);
      }
      return true;
}

-Martin

18.04.2005 - 22:54 Uhr

Hallo.

Michael Willers hat Ende letzten Jahres eine MSDN TechTalk Veranstaltung zum Thema Sicherheit gemacht. Dabei hat er Demo-Code geschrieben mit dem man auf die ACL's Zugreifen. Ich glaube da war auch der Sicherheitszugriff auf Dateien dabei.

Download unter
http://www.techtalk.ms/Default.aspx?tabid=33

Ansonsten ist ab .NET 2.0 wie erwähnt ein Wrapper um die ACL API gelegt worden, der sicherlich abhilfe schaffen wird.

-Martin Ehrlich

18.04.2005 - 22:36 Uhr

Hallo.

Ich bin kein C++ oder Interop Profi, aber soviel ich gehört habe kommt es darauf an, um was für einen Compiler es sich dabei handelt. Ist es Visual Studio 2003 (C++) selbst, gibt es zwei Wege, IJW (It-Just-Works) eine Art C++ Interop und Alternativ COM. Einen direkten Weg gibt es nicht, was mit dem IL-Code zusammenhängt.

Andere C++ Compiler bzw. bestehende Programme können wohl soweit ich gehört habe lediglich via COM mit Managed Code kommunizieren.

Als groben Überblick kann die eine Webcast Reihe bei Microsoft empfehlen, eine der Folgen beschäftigt sich mit API Design in Zusammenhang mit COM und C++ Interop.

Zu finden unter
http://msdn.microsoft.com/netframework/programming/classlibraries/understandinginteroperability/

(Die anderen Webcasts in dieser Reihe sind übrigens auch sehenswert, auch wenn vieles in den Guidlines zur .NET Programmierung nachzulesen ist. Aber hier und dort gibt auch für Profis die eine oder andere nützliche Info.)

Ansonsten gibt es sicherlich Tonnen an Informationen zum Thema Interop im MSDN. Denn schließlich muß auch Microsoft seine bestehende Unmanaged Software (Office, Exchange, Windows, IIS, u.s.w.) weiterhin mit der Managed Welt verbinden. Und es wird wohl noch etwas Dauern bis es ein Managed Office oder Managed Exchange oder Managed Windows geben wird (nun ja WinFX/Longhorn läßt schonmal Grüßen.)

-Martin Ehrlich

13.04.2005 - 15:18 Uhr

Hallo.

Aus einer Web-Anwendung heraus eigentlich garnicht. Denn diese Anwendung läuft auf dem Webserver, die Ausgaben werden via Http an den Browser zum darstellen geschickt.

Es gibt keine Möglichkeit server-seitig einen Client zum Ausführen von Programmen zu bringen. Die einzige Abhilfe aus einer Webseite heraus eine Anwendung der Workstation in Gang zu setzen würde JavaScript und ActiveX erforderlich machen, die in den Code der Webseite integriert sind.

Diesem steht jedoch das Sicherheitsmodell eines jeden Browsers entgegen (außerdem muß er auch ActiveX beherrschen, was nicht jeder kann).

Was bleibt ist es einen normale Client-Anwendung zu schreiben, z.B. eine Windows-Forms Anwendung oder eben eine, die in der Kommandozeile läuft. Diese können Problemlos neue Prozesse in Gang setzen.

-Martin Ehrlich

12.04.2005 - 22:10 Uhr

Hallo.

Es ist zwar nicht die feine englisch Art auf zukünftige Versionen bzw. Beta-Versionen zu verweisen, aber das Problem ist mit ASP.NET Version 2.0 mit dem Feature "Master-Pages" elegant gelöst. Auch das mit dem Pfad, überhaupt auch mit Navigation, Anmeldung, Gruppenmitgliedschaft, u.s.w.

Microsoft wird aller Voraussicht nach Ende dieses Monats die Beta 2 von "ASP.NET 2.0" veröffentlichen zusammen mit einer Go-Live Lizenz, die es erlaubt produktiv damit zu arbeiten. Aus eigener Erfahrung weiß ich, das die Beta 1 bereits sehr gut funktioniert.

Da die Thematik sich damit in Zukunft mit Bordmitteln lösen läßt, würde ich mir jetzt eher die Mühe machen mich mit ASP.NET 2.0 zu beschäftigen. Derzeitig gibt es mehr und mehr Artikel zu diesen Themen und bei Microsoft kann man sich die Beta-Version von "Visual Web Developer Express 2005" herunterladen, man der man schon mal üben kann, bis die Beta 2 kommt.

-Martin Ehrlich

12.04.2005 - 21:59 Uhr

Hallo.

Die Sache mit der MAPI-Programmierung hat einen unangenehmen Nebengeschmack, die MAPI selbst. Das Arbeiten damit ist so eine Sache für sich und hat sofort immer mit Interop zu tun. Außerdem braucht man eine Installation von Outlook auf der Client-Station und ein eingerichtetest MAPI-Profil.

Bedeutend flexibler geht es über WebDAV. Hier findet der Austausch via Http-Anfrage mit dem Webserver von Exchange statt. Darüber läßt sich fast alles erledigen. Das beste Beispiel ist der Outlook Webaccess, der selbst über WebDAV mit Exchange kommuniziert.

Das einzige was man dafür braucht ist etwas HTTP, das Ergebnis kommt als Xml zurück. Kein Outlook, keine MAPI, keine Zusatzlizenz, kein COM, kein Interop.

Informationen dazu finden sich im Exchange SDK unter:
http://www.microsoft.com/downloads/details.aspx?FamilyId=E7E34B5B-01B0-45ED-B91F-F7064875D379&displaylang=en

-Martin Ehrlich

12.04.2005 - 21:51 Uhr

Hallo NG.

Indexer zu implementieren gehört eher zu den sog. API-Entwicklern, d.h. Entwicklern, die Schnittstellen/Objekte für andere erstellen und für Entwickler, die in ihren Anwendungen Business-Objekte einsetzen, z.B. um die Datenzugriffsschricht (data layer) von der Anwendungslogik zu trennen (business layer).

Entwickle ich eine neue Business-Klasse für irgend einen Anwendungsfall, so benötigt ich meist auch eine Klasse, die für meine Datenklasse ein Behälter darstellt, eine sog. Collection-Klasse.

Ein kleines Beispiel, eine Klasse, die eine Person darstellen soll.


public class Person
{
	public Person()
	{
		_UID = Guid.NewGuid();
	}

	private Guid _UID = Guid.Empty;
	public Guid UID
	{
		get { return _UID; }
		set { _UID = value; }
	}

	private string _Name = String.Empty;
	public string Name
	{
		get { return _Name; }
		set { _Name = value; }
	}

	private DateTime _Geburtsdatum = DateTime.MinValue;
	public DateTime Geburtsdatum
	{
		get { return _Geburtsdatum; }
		set { _Geburtsdatum = value; }
	}

}
}

Nun brauche ich etwas, in der ich eine Reihe von Personen ablegen kann, die Collection-Klasse. Da ich die Liste dynamisch füllen möchte, brauche ich etwas, was sich dynamisch anpasst. Dafür könnte man die ArrayList nehmen.


ArrayList personen = new ArrayList();

Person person = new Person();
person.Name = "Martin";
personen.Add(person);

person = new Person();
person.Name = "Manfred";
personen.Add(person);

Möchte ich nun auf eine Person zugreifen, so verwende ich folgendes:


Person personMartin = (Person)personen[0];

So wirklich Spaß damit macht es nicht, weil ArrayList Variablen vom Typ object speichert. Daher muß ich casten. Und was viel Schlimmer ist, von folgendem Code beschützt mich das nicht.


personen.Add(new XmlDocument());

Ich kann alles reintun was ich will, weder die Liste, noch der Compiler beschweren sich drüber. In diesem Punkt wird die nächste Version von .NET "Whidbey" einige Verbesserungen mit sich bringen in Form von Generics.

Daher denke ich meist als nächstes über eine eigene sog. streng-typisierte Collection nach. Das wäre in diesem Fall eine Collection, in die ich nur Personen reinlegen darf und von der ich immer Personen zurückbekomme. Dafür bietet sich die abstrakte Klasse CollectionBase an.


public class PersonCollection : System.Collections.CollectionBase
{
	public int Add(Person value)
	{
		return List.Add(value);
	}

	public void Remove(Person value)
	{
		List.Remove(value);
	}

	public int IndexOf(Person value)
	{
		return List.IndexOf(value);
	}
}

Dieser Klasse fehlt aber noch etwas, ich kann noch nicht drauf zugreifen. Unter VB6 Zeiten gab es hier oft die Methode Item.


public Person Item(int index)
{
	return (Person)List[index];
}

Das abrufen findet dann wie folgt statt:


PersonCollection personen = new PersonenCollection();

// Hinzufügen der Personen. Siehe oben.

Person personMartin = personen.Item(0);

Prinzipiell okay, aber es geht besser mit einem Indexer. Statt der Methode Item(int), füge ich einen Indexer hinzu.


public Person this[int index]
{
	get { return (Person)List[index]; }
	set { List[index] = value; };
}

Der Zugriff ist nun viel direkter:


Person personMartin = personen[0];

Aber das ist noch nicht alles, ich könnte den Index so erweitern, daß er auch auf einen Namen reagiert.


public Person this[int index]
{
	get { return (Person)List[index]; }
	set { List[index] = value; };
}

public Person this[string name]
{
	get
	{
		string lowerName = name.ToLower();
		foreach (Person person in List)
		{
			if (person.Name.ToLower() == lowerName)
			{
				return person;
			}
		}
		return null;
	}
}

Damit reagiert meine Klasse im Zweifel auch auf:


Person personMartin = personen["Martin"];

Und hat damit den Charakter eines assoziativen Arrays (das man auch über den Namen ansprechen kann).

Das ganze ließe sich noch weiter erweitern. Beispielweise eine Überladung, die nach der UID der Person sucht und die entsprechende Person zurückgibt, oder null, falls nichts zu finden war.

Man kann auch Indexer aus meheren Parametern aufbauen:


public Klasse this[int param1, string param2]
{
	// ...
}

Denn genau genommen ist ein Indexer nur eine Methode die keinen Namen hat.

Noch ein Kommentar zu Collection-Klassen:

Überhaupt ist das Konzept, einen eigenen (typisierten) Behälter für seine Klassen zu haben meist ein sehr vorteilhaftes Design. Denn man kann der Collection-Klasse jede Menge (Business-)Logik beibringen, die speziell auf einen Behälter für seine Objekte passt. Passend zum Beispiel: "Gebe mir das Durchschnittsalter aller Personen." oder falls es ein Feld "Geschlecht" gibt: "Gebe mir die Anzahl der Frauen/Männer." Oder "Geben mir das Durchschnittsalter aller Frauen", und so weiter.

Dieses Anwendungsdesign ist sehr flexibel und leicht erweiterbar. Und selbst mit der nächsten Generation von .NET und Generics haben typisierte Collections weiterhin diese Vorteile und sind daher auch in Zukunft nicht wegzudenken.

-Martin Ehrlich

12.04.2005 - 20:55 Uhr

Hallo,

die Signatur der Methode sagt mir, das die Anwendung innerhalb einer ASP.NET Webanwendung läuft. Dort hat die Klasse Process nichts verloren, es sei denn man will auf dem Webserver eine Anwendung starten.

Die Klasse Process sollte nur in Windows- und Consolen-Anwendungen verwendet werden.

-Martin Ehrlich

11.04.2005 - 21:16 Uhr

Hallo.

Ich traue dieser Angabe nicht!


process.StartInfo.FileName = "./transformtool.hlp";  

Option a:
Vielleicht stimmt das aktuelle Verzeichnis nicht. In der IDE ist das "Debug" oder "Release". Liegt die Datei im Source-Verzeichnis stimmt der Pfad nicht.

Option b:
Die Prozess-Klasse mag den "/" nicht.

Option c:
Die Process-Klasse wandelt "/" in "&quot; um. Das gibt dann noch andere Effekte. Denn vielleicht macht er dann ".<TABULATOR>ransformtool.hlp" denn \t ist ein Platzhalter.

Option d


process.StartInfo.UseShellExecute = false;

Das sorgt dafür, das der Aufruf an eine Anwendung gehen muß. Eine HLP-Datei ist das nicht, daher schließe ich mich der Lösung an, die zugehörige Exe selbst aufzurufen.

Ach ja, ich hoffe der Aufruf ist in einer Consolen oder Windows-Anwendung, nicht in einer ASPX-Seite 🙂

-Martin Ehrlich

11.04.2005 - 21:03 Uhr

Hallo.

Es hängt davon ab, was in dem Datenstrom enthalten ist. Wenn du weisst das es ein Text ist, dann mußt du einen der Encoder bemühen. Hier hängt es davon ab, wie der Text seinerzeit abgelegt wurde (ANSI, UTF8, UTF16, u.s.w.).

Wenn es hingegen einfach nur Binärdaten sind, kann man auf die Decoder bzw. Encoder verzichten.

Beispiel (wenn UTF8-Text drin ist):


using (FileStream fs = File.OpenRead("file.txt"))
{
   StreamReader reader = new StreamReader(fs, System.Text.Encoding.UTF8);
   string text = reader.ReadToEnd();
   // oder eben reader.ReadLine() bis null zurückkommt.
}

Binär sieht es etwas anders aus, hier kann man direkt FileStream verwenden oder eben den BinaryReader/BinaryWriter. Direkt mit FileStream machts nicht so viel Spaß, da nimmt man wirklich besser den BinaryReader


using (FileStream fs = File.OpenRead("file.txt"))
{
  BinaryReader reader = new BinaryReader(fs);
  // Warnung: Besser nicht mit zu großen Dateien!
  byte[] buffer = new byte[(int)fs.Length];
  buffer = reader.ReadBytes((int)fs.Length);
  // irgendwas mit buffer machen, z.B. zurückgeben
}

Grundsätzlich ist das System sehr durchdacht, auch wenn es auf den ersten Blick anders scheint, aber spätestens, wenn man versucht, einen Text in UTF8 abzulegen, zu komprimieren (GZIP), dann zu verschlüssel und schließlich via TCP/IP Verbindung zu verschicken, zeigt sich, wie mächtig das Stream-Konzept wirklich ist.

-Martin Ehrlich

10.04.2005 - 23:43 Uhr

Hallo,

evtl. prüfen, ob die Assembly "System.Web" überhaupt referenziert wird. Dies ist nur bei Webservices und Webanwendungen der Fall, sonst nicht (verlängert sonst unnötig die Ladezeit einer Anwendung).

Daher "Add Reference ..." > ".Net" > "System.Web.dll".

-Martin

10.04.2005 - 17:44 Uhr

Hallo CaptainIglo,

für den Zugriff auf Daten unter .NET bentötigt es immmer:

Connection
Command

Und je nach Zugriff: DataAdapter/DataSet oder DataReader.

Da es aber verschiedene Wege gibt an Daten heranzukommen (Nativ, OLEDB, ODBC) gibt es die oben beschriebenen Klassen für jede Variante einzeln.

Nativ ist z.B. der SqlClient für den MSSQL Server, dieser fängt mit dem Prefix "Sql" an, d.h. es gibt einen:

SqlConnection
SqlCommand
SqlDataAdapter
SqlDataReader

Für Datenbanken für den ein OLEDB-Treiber vorliegt verwendet man entsprechend:

OleDbConnection
OleDbCommand
OleDbDataAdapter
OleDbDataReader

Und dann gibt es das noch für Datenbanken, für die nur ein ODBC-Treiber vorliegt:

OdbcConnection
OdbcCommand
OdbcDataAdapter
OdbcDataReader

d.h. die Hauptfrage ist zunächst welche Treiber es gibt. Was MySQL angeht ist mir bekannt das es einen MySql eigenen ODBC Treiber gibt. Man kann aber auch einen OLEDB Treiber für Windows finden. Und ich habe gehört es gibt kommerzielle Produkte sowieso OpenSource die einen nativen Treiber für MySQL anbieten.

Warum nativ? Nun, diese Treiber sprechen in der Regel direkt mit der jeweiligen Datenbank ohne Umwege über das ODBC-System oder COM (OLEDB). Daher sind "native" Treiber meist auch die performatesten.

Ich weiß von einem Freund, das man mit dem ODBC Treiber von MySQL von .NET aus durchaus weiterkommt ohne Probleme. In diesem Fall kommen alle Klassen mit "Odbc" am Anfang in Frage.

Um die Daten nun zu bekommen braucht es drei Schritte:

  • Verbindung intialisieren
  • SQL-Befehl ausführen
  • Daten abfragen

using(OdbcConnection connection = new OdbcConnection(<ConnectionString>))
{

using(OdbcCommand command = new OdbcCommand("SELECT ID, Name, GebDatum FROM MeineTabelle", connection))
{

OdbcDataReader reader = command.ExecuteReader();

// Ausgeben/Verarbeiten der Daten

int columnIndexGebDatum = reader.GetOrdinal("GebDatum");
while (reader.Read())
{
// Mölichkeit 1:
Console.WriteLine((int)reader[0]);

// Möglichkeit 2:  
Console.WriteLine(reader.GetString(1));  
  
// Möglichkeit 3:  
Console.WriteLine(reader.GetString(columnIndexGebDatum));  

}
reader.Close();

}

}

Das ist nur ein erster flüchtiger Eindruck auf die ganze Thematik, unter den Strichpunkt "ADO.NET" im SDK oder auf msdn.microsoft.com gibt es noch Unmengen mehr an Informationen zu diesem Thema.

MFG

Martin Ehrlich

10.04.2005 - 17:24 Uhr

Hallo Harry,

die Sache ist eigentlich ganz einfach. Jede Variable hat einen bestimmten Geltungsbereich, im englischen nennt man das Scope.

Definiere ich eine Variable auf Klassen-Ebene, dann ist se in alle Methoden, Properties, Indexern, u.s.w. "sichtbar". Definiert man eine Variable hingegen innerhalb einer Methode, ist diese auch nur innerhalb der Methode "sichtbar". Daher nennt man diese Variablen auch "lokale" Variablen.

Daher muß der Code umgeschrieben werden:

public class HalloHarry
{

string STRING = "Hallo";

public void command02()
{
if (STRING == "Hallo")
command03();
}

public void command03()
{
if (STRING == "Hallo")
MessageBox.show ("Harry ist doof");
}
}

Noch etwas, der Variablen-Namen ist etwas unglücklich gewählt. Man sollte keine reservierten Wörter als Variablenamen einsetzen (auch wenn in C# zwischen "string" und "STRING" ein unterschied besteht, in VB.NET gibt es diesen nicht.

Es gibt zur Namensgebung einen ganzen Ratgeber unter:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconnamingguidelines.asp

MFG,

Martin Ehrlich