Okt 212010
 

In einem aktuellen Projekt generieren wir zu Laufzeit eine Access DB dessen Daten für Reporte (Crystal Report VS 2010 BETA 2) genutzt werden. Die Access DB wird dabei im Temp-Ordner des Benutzers abgelegt und bekommt zudem noch einen Unterordner in der die aktuelle Zeit drin ist. Bis auf den Access DB Name ist also alles ziemlich dynamisch.

Angezeigt werden die Reporte mit dem WPF Control das es seit der BETA 2 gibt. Bei gewissen Reports, vor allem solche mit Subreports, wurde man beim generieren des Reports nach dem Login für die Access DB gefragt. Die Access DB ist aber ganz bestimmt nicht Passwort geschützt. Das kann also gar nicht sein.

Beim beobachten der Anwendung mit dem “Process Monitor” von Microsoft, ehemals Sysinternals,  zeigt sich dann auch wie so dem so ist. Der Viewer suchte die Access DB nicht dort wo sie war, sondern ganz wo anders. Das musste geändert werden. Aber wie?

Im Internet gibt es 1000 Hinweise was zu tun ist. Aber nichts, einfach gar nichts, sagt einem genau wie es zu machen ist. Hier die Lösung.

m_Report = new ReportDocument();
m_Report.Load(m_Model.ReportFilePath);

//Die neue Datenquelle muss auf allen Subreports gesetzt werden. 
//WICHTIG: ZUERST AUF DEN SUBREPORTS. ERST DANACH AUF DEM MAINREPORT
foreach (ReportDocument subreport in m_Report.Subreports)
{
    foreach (Table table in subreport.Database.Tables)
    {
        TableLogOnInfo logOnInfo = table.LogOnInfo;

        logOnInfo.ConnectionInfo.AllowCustomConnection = true;
        logOnInfo.ConnectionInfo.Type = ConnectionInfoType.DBFile;
        logOnInfo.ConnectionInfo.ServerName = m_Model.ReportDatabasePath;
        logOnInfo.ConnectionInfo.DatabaseName = "RDReport.mdb";
        logOnInfo.ConnectionInfo.IntegratedSecurity = true;

        table.ApplyLogOnInfo(logOnInfo);
    }
}

//Nach den Subreports auch die Mainreports anpassen.
foreach (Table table in m_Report.Database.Tables)
{
    TableLogOnInfo logOnInfo = table.LogOnInfo;

    logOnInfo.ConnectionInfo.AllowCustomConnection = true;
    logOnInfo.ConnectionInfo.Type = ConnectionInfoType.DBFile;
    logOnInfo.ConnectionInfo.ServerName = m_Model.ReportDatabasePath;
    logOnInfo.ConnectionInfo.DatabaseName = "RDReport.mdb";
    logOnInfo.ConnectionInfo.IntegratedSecurity = true;

    table.ApplyLogOnInfo(logOnInfo);
}

MyCrystalReportViewer.ViewerCore.ReportSource = m_Report;
Wichtig 1

Zuerst MÜSSEN die Subreports angepasst werden. Erst danach der Hauptreport. Sonst passiert gar nichts. Deshalb zuerst die Subreports durchlaufen und auf allen Tabellen die ConnectionInfo via TableLogInInfo anpassen.

Wichtig 2

Was ich auch fast nirgends erwähnt fand ist der Hinweis, dass man AllowCustomConnection und Type setzten muss. Sonst läuft die Sache auch nicht.

In fast allen Beispielen wird nur der ServerName und der DatabaseName erwähnt. Zudem noch der UserId und das Password. Fast alle Beispiele die ich gefunden habe nutzen nicht IntegratedSecurity. Diese Flagg muss also gesetzt werden.

Auch sehr verwirrend ist der ServerName. Bei einem SQL Server ist das ja noch klar. Aber bei einer Access DB?

ServerName: Bei einer Access DB der ganze Pfad mit dem Dateinamen. Also z.B.: D:\Folder\Access.mdb

DatabaseName: Das ist jetzt nur die Access DB. Also z.B.: Access.mdb

Aus etwa 30 Quellen im Internet habe ich diese Lösung zusammen getragen. Ich hoffe es hilft jemandem damit er schneller an sein Ziel kommt.

C# – Generisch serialisieren und deserialisieren

 Entwicklung  Kommentare deaktiviert für C# – Generisch serialisieren und deserialisieren
Nov 232009
 

Etwas, was ich in der Softwareentwicklung häufig brauche, ist das Serialisieren und Deserialisieren von Objekten.

Über dieses Thema habe ich im Jahre 2005 auch schon mal was geschrieben.

Objekt serialisieren und deserialisieren

Seit dieser Zeit hat sich aber einiges getan. .NET wurde um die Generics erweitert. Damit lassen sich ganz schöne Sachen machen.

Zum Beispiel, generisch serialisieren und deserialisieren

public static void Save<T>(T objectForSave, string fileName)
{
	using(FileStream fs = new FileStream(fileName, FileMode.Create))
	{
		XmlSerializer xml = new XmlSerializer(typeof(T));
		xml.Serialize(fs, objectForSave);

		fs.Flush();
	}
}

public static T Load<T>(string fileName)
{
	using(FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
	{
		XmlSerializer xml = new XmlSerializer(typeof(T));
		return (T)xml.Deserialize(fs);
	}
}

In diesem Beispiel wird der XmlSerializer verwendet. Allerdings kann man auch den BinaryFormatter für die Aufgabe her nehmen.

Der Einsatz der Methoden ist sehr einfach.

Foo foo = new Foo();
Save<Foo>(foo, "c:\foo.txt");
foo = Load<Foo>("c:\foo.txt");

kick it on dotnet-kicks.de

Sep 092009
 

Bei einer Anwendung werden Mitarbeiter via einer Datei importiert. Namen und Vornamen können durchaus Umlaute enthalten.

Ursprünglich wurden die Namen mit korrekten Umlauten exportiert. Irgend wann stellte der Exporteur aber das Encoding um. Danach waren die Umlaute nicht mehr richtig in der Exportdatei. Das sah wie folgt aus.

103129,Roland,Meier,Gnter H”ller

oder

"000000000  31 30 33 31 32 39 2C 52-6F 6C 61 6E 64 2C 4D 65   |103129,Roland,Me|"
"000000010  69 65 72 2C 47 81 6E 74-65 72 20 48 94 6C 6C 65   |ier,Gnter H”lle|"
"000000020  72                                                                         |r               |"

Jetzt wollte aber die Importroutine natürlich die Umlaute auch nicht mehr richtig importieren. Das führte unweigerlich zu Problemen.

using (StreamReader sr = new StreamReader(file, Encoding.Default))
{
	string row = sr.ReadLine();

	while (sr.Peek() >= 0)
	{
		row = sr.ReadLine();
		string[] fields = row.Split(',');

Mit diesem Code wurden die Umlaute nicht richtig importiert. Bei Encoding.XXX habe ich alle durchprobiert. UTF7, UTF8, UTF32, Default und alles Andere noch. Aber die Umlaute kamen noch immer falsch rein.

Irgend wie kam ich nicht auf die richtige Idee. Also fragte ich einfach mal bei CodeKicker.de nach. Nach kurzer Zeit hatte dort jemand die richtige Idee.

using (StreamReader sr = new StreamReader(file, Encoding.GetEncoding(437)))

Wenn der StreamReader mit diesem Encoding geöffnet wird klappt es wunderbar.

kick it on dotnet-kicks.de

Jun 162009
 

Seit kurzem gibt es bei mir im Blog oben Rechts den Hinweis wie viele Besucher zur Zeit bei mir Online sind. Das Ding ist aber sehr einfach gestrickt, sollte aber relativ genau sein. Wer mehr Hintergrundwissen haben möchte kann sich bei Klaus Bock dieses Posting ansehen.

Klaus geht noch weiter. Er registriert ein Java Script auf dem Client das beim schliessen des Browsers noch eine Seite aufruft die nichts anderes macht als die Session auf dem Server zu beenden. Aber spätestens nach 20 Minuten wird die Session auf dem Server so oder so mit Session_End beendet.

Die Implementation in BlogEngine.NET, kurz BE, läuft in etwa wie folgt ab.

Bei BE sind die Sessions standardmässig ausgeschaltet. Ich habe sie einfach in der Web.config wieder eingeschaltet. 

<sessionState mode="InProc" cookieName=".geniali.ch"/>

Als nächstes muss noch bemerkt werden wann eine neue Session startet und wann eine Session beendet wird. Dazu gibt es in ASP.NET zwei Events die man in der Datei global.asax implementieren muss. Bei BE sind die Zwei noch nicht drin.

void Session_Start(object sender, EventArgs e)
{
    // User-Zähler erhöhen.  
    Application.Lock();
    Application["Viewers"] = (int)Application["Viewers"] + 1;
    Application.UnLock();
}

void Session_End(object sender, EventArgs e)
{
    // User-Zähler verringern.  
    Application.Lock();
    if((int)Application["Viewers"] > 0)
    {
        Application["Viewers"] = (int)Application["Viewers"] - 1;
    }
    Application.UnLock();
}

Das Application["Viewers"] Objekt muss aber zuerst noch initialisiert werden. Zudem muss auch sichergestellt sein das beim Start des Webs diese Variable auch wieder auf 0 gestellt wird. Dies macht man im Event Application_Start.

void Application_Start(object sender, EventArgs e)
{
    // Schlüssel Viewers in der Anwendung erzeugen
    Application["Viewers"] = 0;
		...

Danach muss nur noch sicher gestellt werden das die “Viewers” auch angezeigt werden. Dazu erstellt man ein neues UserControl. Ich habe das UserControl unter meinem Theme im Ordner Controls abgelegt.

UserOnline.ascx

<%@ Control Language="C#"
 AutoEventWireup="true" 
CodeFile="UserOnline.ascx.cs" 
Inherits="themes_GENiALi_Controls_UserOnline" %>
<asp:Literal ID="litUsers" runat="server"></asp:Literal>

UserOnline.ascx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class themes_GENiALi_Controls_UserOnline 
	: System.Web.UI.UserControl
{
	protected void Page_Load(object sender, EventArgs e)
	{
		litUsers.Text = Application["Viewers"].ToString() 
			+ " Besucher online";
	}
}

Das UserControl kann man einfach auf die site.master des Theme ziehen und dort platzieren. Mit CSS kann die Anzeige noch formatiert und ausgerichtet werden.

Unter dem Strich sollte es in etwa so aussehen. 🙂

8 Besucher Online

kick it on dotnet-kicks.de

ASP.NET – MaxRequestLength auf der Webseite anzeigen

 Entwicklung  Kommentare deaktiviert für ASP.NET – MaxRequestLength auf der Webseite anzeigen
Mai 272009
 

Ich schraube zur Zeit an einer Anwendung rum die einem die Möglichkeit gibt Dateien hoch zu laden. Diese werden dann in einem SQL Server gespeichert.

Standardmässig begrenzt ASP.NET die Dateigrösse auf 4 MB (4096 KB). Das kann man mit diesem Eintrag hier ändern.

[...]
<system.web>
    <!-- Uploadgrösse anpassen. Standart ist etwa 4 MB in KB (4MB = 4096KB)-->
    <httpRuntime maxRequestLength="8192" />
[...]

Es macht deshalb auch Sinn das man die User über die maximale Grösse der Datei infomiert. Ich will also anzeigen wie gross eine Datei sein darf. Ich will aber nicht bei jeder Änderung des Wertes das HTML anpassen müssen. Also muss es im Code machbar sein.

Zum Beispiel so:

Configuration config = WebConfigurationManager.OpenWebConfiguration("~");
HttpRuntimeSection section = config.GetSection("system.web/httpRuntime") as HttpRuntimeSection;
double maxFileSize = Math.Round(section.MaxRequestLength / 1024.0, 1);

labFileSize.Text = string.Format("{0:0.#} MB", maxFileSize);

Wenn es noch einfacher geht lasst es mich wissen.

kick it on dotnet-kicks.de

Apr 282009
 

Bei ZDNet.de hat ein Justin James ein Beitrag veröffentlicht der genau dieser Frage nachgeht.

Technisch gibt es keinen Grund. Vom Funktionsumfang auch nicht. Persönliche Vorliebe für eine Sprache ist ganz klar ein Grund.

Aber wie so soll ein VB.NET Entwickler schlechter sein als ein C#? Woher kommt dieses Vorurteil?

Eine Sichtweise hat er vergessen. Genau deshalb habe ich nicht VB.NET gelernt sondern C#.

VB ist eine Ausbildungssprache. Ganz einfach gesagt, sie muss einfach sein damit man sie einfach erlernen kann. Eine Sprache für Schüler und solche in Ausbildung. Sie war erfolgreich weil man sie einfach erlernen konnte.

Und genau deshalb wollte ich nie was mit VB zu tun haben. NIE. Ich will es auch heute nicht wirklich. Und keiner der 8 anderen Programmierer ist begeistert wenn er was mit VB oder VB.NET zu tun hat. Es gibt eine allgemeine Ablehnung.

Bei mir ist es die Vergangenheit von VB.

Dazu kommt natürlich auch die Hilflosigkeit wenn man mal wieder was mit VB machen sollte. Die unnötigen ; am Ende einer Zeile. Oder beim if die { Klammer. Alles ist irgend wie anders.

DotNetKicks-DE Image