Objekte und Listen lassen sich jetzt in C# 3.0 sehr schön initialisieren. War bisher um ein relativ einfaches Objekt zu initialisieren sehr viel Code erforderlich, hat sich das nun deutlich verkürzt: Bisher: List<cProduct> Products = new List<cProduct>();
cProduct Product = new cProduct();
Product.ID = 1;
Product.BusinessDevelopmentComment = "";
Product.MarketingComments = "";
Product.VersionDescriptions = new List<cProductVersionDescription>();
Product.INNs = new List<cProductINNItem>();
cProductVersionDescription VersionDescription = new cProductVersionDescription();
VersionDescription.ID = 1;
VersionDescription.LastPCMDate = DateTime.Now;
VersionDescription.LastPCMDecision = "Go";
VersionDescription.NPV = 689.54;
VersionDescription.ProductID = 1;
VersionDescription.ProductVersionID = 1;
VersionDescription.VersionName = "Version1";
Product.VersionDescriptions.Add(VersionDescription);
cProductINNItem INN = new cProductINNItem();
INN.INN = 8;
INN.Dosage = 1.5;
INN.DosageUnit = 1;
INN.Volume = 1;
INN.Comment = "Test-Comment";
INN.INNOrder = 1;
Product.INNs.Add(INN);
INN = new cProductINNItem();
INN.INN = 2;
INN.Dosage = 2.5;
INN.DosageUnit = 2;
INN.Volume = 2;
INN.Comment = "Test-Comment2";
INN.INNOrder = 2;
Product.INNs.Add(INN);
Neu mit C#3.0:
List<cProduct> Products = new List<cProduct>
{
new cProduct{ ID = 1, BusinessDevelopmentComment = "", MarketingComments = "",
VersionDescriptions = new List<cProductVersionDescription>
{
new cProductVersionDescription{ ID=1, LastPCMDate=DateTime.Now, LastPCMDecision="Go", NPV=689.54, ProductID=1, ProductVersionID=1, VersionName="Version1"}
},
INNs = new List<cProductINNItem>
{
new cProductINNItem{ INN=8, Dosage=1.5, DosageUnit=1, Volume=1, Comment="Test-Comment", INNOrder=1},
new cProductINNItem{ INN=2, Dosage=2.5, DosageUnit=2, Volume=2, Comment="Test-Comment2", INNOrder=2}
}
}
};
Bisher konnte man den Code noch etwas vereinfachen, indem man einen entsprechenden Konstruktor für die Objekte erstellt hat, in dem die Initialisierungswerte übergeben werden konnten. Hier bietet die neue Version aber den Vorteil, dass diese leichter zu leesen ist, da die Parameter hier entsprechend bezeichnet sind. Beim Konstruktor musste immer Intellisense zu Hilfe genommen werden um herauszufinden, um welchen Parameter es sich handelt.
Möchte man ASP.Net Anwendungen mit VS2008 debuggen, dann ist entscheidend, welchen Servermode man in den Einstellungen für das Web-Projekt ausgewählt hat. Mit dem Visual Studio Developer Server hat das bei mir auf anhieb funktioniert. Allerdings mit dem IIs gab es Probleme. Hier habe ich zuerst diese NMeldung bekommen: Unable to start debugging on the web server. Strong name validation failed. Diesen Fehler kann man beheben indem man folgendes Procedere durchführt: - Visual Studio beenden!
- Den "Visual Studio Command Prompt" mit "Run as Administrator" starten.
- Dann folgenden Befehl ausführen: sn.exe -Vr "%ProgramFiles%\Microsoft Visual Studio 9.0\Common7\IDE\iisresolver.dll"
Damit sollte diese Meldung behoben sein. Dann kam aber bei mir der nächste Fehler: Unable to start debugging on the web server. IIS does not list an application that matches the launched URL. Um diesen Fehler zu beseitigen muss man die Windows Authentification aktivieren. Dazu geht man folgendermaßen vor: - Im Explorer auf "Computer" Rechtsklick und dann "Manage"
- Den IIS Manager öffnen und die entsprechende Anwendung auswählen
- "Authentication" unter IIS doppelklicken und dann "Windows Athentication" auf Enabled setzen
Damit hat bei mir das Debuggen dann auch mit dem IIS funktioniert.
Ich habe in einer Anwendung ein Objekt, das über ca. 30 List<double> Properties verfügt. Dieses Objekt möchte ich nun mit LINQ to SQL in die Datenbank schreiben. Natürlich unterstützt LINQ 1:n-Beziehungen in Objekten aber in der Datenbank müsste ich dann für jedes List-Property eine eigene Tabelle anlegen und für jedes Property einen eigenen Typ definieren. Das schien mir doch sehr umständlich. Ich habe dann nach einer Möglichkeit gesucht, die List-Elemente etwas effizieter zu speichern, am einfachsten in einem Feld je Liste, da ich die für die Abfrage eh nicht brauche. Aber wie kann ich die Ausgabe von LINQ in die Datenbank steuern? Wie kann ich erreichen, dass LINQ meine Daten entsprechend formatiert? Googeln brachte keine vernünftigen Ergebnisse und sonst bin ich auch nicht fündig geworden, aber dann kam die Idee (manchmal ist es schneller erst nachzudenken bevor man googelt, aber das vergisst man oft ). Die Grundidee war, für jede Liste noch ein zusätzliches Property anlegen, das die Daten im Getter und Setter entsprechend formatiert. Die Hoffnung war, wenn ich dieses Property mit einem Column-Attribut versehe, wird LINQ den formatierten Inhalt schreiben und beim Lesen wieder zurückformatieren. public List<string> Sales { get; set; }
[Column(Name="Sales", DbType = "nvarchar(4000)")]
private string SalesString
{
get
{
return string.Join("|", Sales.ToArray());
}
set
{
if (value != null)
Sales = value.Split('|').ToList();
}
}
Ich habe einfach eine Text-Spalte in der Datenbank verwendet und die einzelnen List-Elemente mit einem Trennzeichen verbunden (der Einfachkeit halber habe ich mit einer List of Strings gearbeitet. Für die Tests ist das ausreichend und sollte auf beliebige andere Typen übertragbar sein).
Einen kleinen Stolperstein gibt es noch. Beim Ausführen erhielt ich folgende Meldung:

LINQ verwendet Optimistic Locking für das Schreiben und hat wohl Probleme, wenn der Inhalt des Property im Getter verändert wird. Abhilfe schafft hier das zusätzliche Attribut UpdateCheck, also sollte das Attribut zu dem Property so aussehen:
[Column(Name="Sales", DbType = "nvarchar(4000)", UpdateCheck=UpdateCheck.Never)]
Hier sind natürlich auch noch andere Möglichkeiten der Datenrepräsentation möglich. Hier mal ein Beispiel mit XML-Serialisierung und diesesmal auch mit Doubl-Werten.
public List<double> Sales { get; set; }
[Column(Name="Sales", DbType = "xml", UpdateCheck=UpdateCheck.Never)]
private string SalesString
{
get
{
UTF8Encoding encoding = new UTF8Encoding();
MemoryStream ms = new MemoryStream();
XmlSerializer xmlSer = new XmlSerializer(typeof(List<double>));
xmlSer.Serialize(ms, Sales);
return encoding.GetString(ms.ToArray());
}
set
{
if (value != null)
{
UTF8Encoding encoding = new UTF8Encoding();
MemoryStream ms = new MemoryStream(encoding.GetBytes(value));
XmlSerializer xmlSer = new XmlSerializer(typeof(List<double>));
Sales = (List<double>)xmlSer.Deserialize(ms);
}
}
}
Dank an Bernhard Gojer für den Lösungsansatz.
Vom 10.Oktober bis 12.Oktober findet in Berlin die XTOPIA statt. Dabei handelt es sich um eine Web-Konferenz von Microsoft, die sich an Entwickler, Entscheider und Designer richtet. Im Zentrum dabei steht Silverlight.
Ich werde auf der XTOPIA als ATE (Ask The Expert) zum Thema Visual Studio Team System vertreten sein.

Nutzt man LINQ in einer klassischen 2-tier Anwendung, dann funktioniert das wunderbar. Nur leider ist das eher ein Auslaufmodell. Bei modernen Anwendungen kommt häufig ein 3-tier Ansatz vor (SOA etc.). An einer kleinen WCF-Anwendung möchti ich das Problem kurz schildern: Eine kleine Anwendung soll Daten darstellen und bearbeiten können. nehmen wir einfach mal eine Adress-Verwaltung. Die Anwendung soll service-orientiert augebaut sein, d.h. es gibt einen zetralen Service, der die Schnittstelle zur Datenbank implementiert. Soll nun eine Liste der Adressen geladen werden, ruft der Client auf dem Service eine entsprechende Methode auf. Diese Methode nutzt nun LINQ um die Adressen aus der Datenbank zu lesen und als List<cAddress> zurückzuliefern. Diese kann der Client nun darstellen. So weit, so gut. Aber was passiert jetzt, wenn eine Adresse bearbeitet wird und diese wieder gespeichert werden soll. Der Client ruft eine entsprechende Methode auf dem Service auf und übergibt das neue Adress-Objekt. Durch die Übertragung zum Client und wieder zurück muss das Objekt serialisiert und wieder deserialisiert werden. Dadurch stellt es eine komplett andere Instanz dar, wenn es wieder beim Service ankommt und hat daher keine Verbindung mehr zum DataContext (vorausgesetzt dieser wäre überhaupt noch vorhanden, dazu müsste der in einer Session aufbewahrt werden, was recht problematisch ist). Wie bekommt man nun das neue Adress-Objekt in die Datenbank? Die von Microsoft vorgschlagene Vorgehensweise geht über die Attach-Methode. Dabei erzeugt man einfach einen neuen DataContext und fügt das bearbeitete Objekt diesem hinzu. Das Problem dabei ist das Change-Tracking des DataContext. Dieser möchte nämlich wissen, was sich an dem Objekt geändert hat. Führt man nur einen Attach durch, dann bewirkt das gar nichts. Man kann entweder das gesamte Objekt als bearbeitet deklarieren, so wie ich das in diesem Post demonstriert habe, oder man muss ein Referenz-Objekt mit angeben, gegen das das Objekt evrglichen werden kann. Eine weitere Möglichkeit ist auch noch die Zuweisung aller geänderten Eigenschaften nach dem Attach, da ab diesem Zeitpunkt das Change-Tracking alle Änderungen registriert. Das ist alles nicht so schön. Mit einem kleinen Trick kan man sich hier die Sache etwas einfacher machen, auch wenn das nicht gerade sauber ist. Man kann das Objekt einfach löschen und dann neu hinzufügen. Das angenehme ist, dass der DataContext das recht intelligent handhabt und intern aus dem Delete and Insert ein Update macht. Der Code sieht dann ungefähr so aus: public void UpdateCompany(LINQTest.Contracts.DataContracts.cContactCompany company)
{
cContactDataContext db = new cContactDataContext("Data Source=NOTEBOOK_VISTA;Initial Catalog=LINQ;Persist Security Info=True;");
cContactCompany OrigCompany = db.Companies.Single<cContactCompany>(c => c.ID == company.ID);
if (OrigCompany != null)
{
db.Companies.Remove(OrigCompany);
}
db.Companies.Add(company);
db.SubmitChanges();
}
Auch wenn das, wie gesagt nicht besonders schön ist, ich konnte noch keine negativen "Nebenwirkungen" feststellen, der Code macht genau das, was ich von ihm erwarte und das auch noch mit relativ schönem SQL, wie mir der SQL-Profiler anzeigt. Erst wird mal ein SELECT gemacht um zu sehen, ob das Element schon vorhanden ist (diesen Punkt kann man sicher noch optimieren):
exec sp_executesql N'SELECT [t0].[ID], [t0].[CompanyName], [t0].[CompanyAddress], [t0].[CompanyTelephone]
FROM [tblCompanies] AS [t0]
WHERE [t0].[ID] = @p0',N'@p0 int',@p0=2
Dann wird der Update ausgeführt
exec sp_executesql N'UPDATE [tblCompanies]
SET [CompanyName] = @p1
WHERE [ID] = @p0',N'@p0 int,@p1 nvarchar(12)',@p0=2,@p1=N'Test-Company'
Das sieht doch eigentlich sehr schön aus.
Problematisch wird die Sache nun, wenn das Objekt Unterelemente enthält, also z.B. Ansprechpartner. Hier gibt es mit dem Attach keine saubere Lösung undauch der Delete and Insert Ansatz versagt hier zunächst. Es wird einfach für alle untergeordneten Elemente ein Insert ausgeführt, egal ob diese bereits existieren oder nicht. Das Ganze kann man lösen, indem man auch die Unterelemente zunächst löscht.
public void UpdateCompany(LINQTest.Contracts.DataContracts.cContactCompany company)
{
cContactDataContext db = new cContactDataContext("Data Source=NOTEBOOK_VISTA;Initial Catalog=LINQ;Persist Security Info=True;User ID=sa;Password=admin");
cContactCompany OrigCompany = db.Companies.Single<cContactCompany>(c => c.ID == company.ID);
if (OrigCompany != null)
{
db.Companies.Remove(OrigCompany);
foreach (cContactPerson p in OrigCompany.Persons)
{
db.Persons.Remove(p);
}
}
db.Companies.Add(company);
db.SubmitChanges();
}
Auch hier wird wieder ein schönes Update daraus gebaut.
Also so richtig toll finde ich das Ganze noch nicht. Hier bleibt zu hoffen, dass Microsoft an dieser Stelle schnellstens noch nachbessert. Das sieht im Moment aber eher schlecht aus, wie man hier und hier lesen kann. Vielleicht hat schon jemand von euch hier Erfahrungen gesammelt. Über einen Kommentar würde ich mich freuen, auch über "Bedenken" bezgl. des Delete and Insert Ansatzes.
Ein Benkannter hatte gerade ein seltsames Problem. Unter VB.Net kann man sehr einfach einen Splash-Screen einrichten. Offensichtlich scheint es da allerdings ein Problem zusammen mit dem Virtual Server zu geben. Jedesmal wenn er die Anwendung innerhalb des Virtual Servers ausgeführt hat, wurde der Splash-Screen angezeigt und er erhielt dann eine Nullreference Exception. In der Host-Umgebung funktionierte das problemlos. Beidesmal kam Win2000 als Betriebssystem zum Einsatz. Hat schn mal jemand so ein Problem gehabt? Hier der Stacktrace: at System.Windows.Forms.Control.WaitForWaitHandle(WaitHandle waitHandle) at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args) at System.Windows.Forms.Control.Invoke(Delegate method) at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.HideSplashScreen() at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.MainFormLoadingDone(Object sender, EventArgs e) at System.EventHandler.Invoke(Object sender, EventArgs e) at System.Windows.Forms.Form.OnLoad(EventArgs e) at System.Windows.Forms.Form.OnCreateControl() at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible) at System.Windows.Forms.Control.CreateControl() at System.Windows.Forms.Control.WmShowWindow(Message& m) at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.ScrollableControl.WndProc(Message& m) at System.Windows.Forms.ContainerControl.WndProc(Message& m) at System.Windows.Forms.Form.WmShowWindow(Message& m) at System.Windows.Forms.Form.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam) at System.Windows.Forms.SafeNativeMethods.ShowWindow(HandleRef hWnd, Int32 nCmdShow) at System.Windows.Forms.Control.SetVisibleCore(Boolean value) at System.Windows.Forms.Form.SetVisibleCore(Boolean value) at System.Windows.Forms.Control.set_Visible(Boolean value) at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.Run(ApplicationContext context) at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.OnRun() at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.DoApplicationModel() at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.Run(String[] commandLine) at SplashScreen.My.MyApplication.Main(String[] Args) in 17d14f5c-a337-4978-8281-53493378c1071.vb:line 81 at System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart()
Ich habe gerade eben eine Seite, die ich mit VS 2008 und dem Framework 3.5 erstellt habe, auf einen Test-Server deployed. Alles hat wunderbar funktioniert, aber bei einer Seite bekam ich die Meldung, dass die Ressource nicht gefunden werden kann. Das konnte aber nicht sein, die Seite war da. Nach einigen Versuchen und einigem Wundern bin ich zufällig auf die Idee gekommen, mir mal den Source der Seite anzuzeigen. Und siehe da, hier steht nun endlich ein brauchbarar Hinweis. Im Source-Code war als Kommentar die Fehlermeldung versteckt. Der Grund war, dass eine Komponente auf ein verzeichnis zugreifen möchte, das auf dem Server nicht existiert. Ich habe versucht, den Fehler mit einer Test-Seite zu reproduzieren, aber erfolglos. Hat jemand eine Idee, woran das liegt und wie man das abstellen kann? Na jedenfalls wenn mal jemand ein ähnliches Problem hat, kennt er jetzt den Workaround. Nicht schön, aber es funktioniert. <html> <head> <title>Die Ressource kann nicht gefunden werden.</title> <style> ... </style> </head> <body bgcolor="white"> <span><H1>Serverfehler in der Anwendung /ValuePlanner_2008.<hr width=100% size=1 color=silver></H1> <h2> <i>Die Ressource kann nicht gefunden werden.</i> </h2></span> ... </body> </html> <!-- [DirectoryNotFoundException]: Ein Teil des Pfades c:\temp\text.txt konnte nicht gefunden werden. bei System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) bei System.IO.File.Delete(String path) ... --><!-- Diese Seite enthält möglicherweise vertrauliche Informationen, weil ASP.NET für die Anzeige ausführlicher Fehlermeldungen mit <customErrors mode="Off"/> konfiguriert ist. In Produktionsumgebungen sollten Sie <customErrors mode="On"/> oder <customErrors mode="RemoteOnly"/> verwenden.-->
Folgende Aufgabenstellung: In einem Infragistics WinGrid soll eine DataTable angezeigt werden. Am Ende des Grids soll eine TemplateAddRow, also eine leere Zeile zum Anlegen einer neuen Row angezeigt werden. Das Infragistics WinGrid unterstützt diese Funktion von Haus aus so, dass man in diese leere Zeile klicken kann und sobald man beginnt hier Werte einzutragen, dann wird in der DataSource tatsächlich eine neue Row angelegt und in dieser die Werte abgelegt. Gleichzeitig wird eine neue TemplateAddRow angelegt, mit der man wieder eine neue Zeile anlegen kann. Was aber nun, wenn man die Werte nicht im Grid selbst eingeben möchte, sondern in separaten Eingabefeldern? Dann geht man folgendermaßen vor: 1.) Zuerst erstellt man eine Form mit einer Bindingsource, einem Grid und diversen Eingabefeldern. Das Grid und die Eingabefelder werden an die BindingSource gebunden. 2.) Dann stellt man das Grid so ein, dass die TemplateAddRow angezeigt wird. this .ultraGrid1.DisplayLayout.Override.AllowAddNew = Infragistics.Win.UltraWinGrid.AllowAddNew.TemplateOnBottom; 2.) Damit nach dem neu Anlegen einer Zeile das Databinding auf den Eingabefeldern funktioniert muss nun manuell eine neue Zeile angelegt werden. Das Grid erzeugt diese Row in der DataSource erst wenn Werte eingegeben werden, das ist für unseren Fall zu spät. Deshalb nutzen wir das BeforeRowIndert Event. private void ultraGrid1_BeforeRowInsert(object sender, Infragistics.Win.UltraWinGrid.BeforeRowInsertEventArgs e) { this.myBindingSource.AddNew(); this.myBindingSource.Position++; e.Cancel = true; } 3.) Jetzt funktioniert bereits das erste Einfügen. Allerdings erscheint danach keine neue TemplateAddRow mehr. Hier muss man einen kleinen Trick anwenden: Man setzt die ActiveRow auf die TemplateAddRow. Dadurch wird eigentlich die TemplateAddRow nun in eine "echte Row" umgewandelt (was wir bereits ja zuvor manuell gemacht haben. Danach erstellt das Grid eine neue TemplateAddRow (das ist genau das was wir brauchen). Etwas unschön ist nun, dass die neue TemplateAddRow automatisch selektiert wird und wir damit schon wieder einen neuen Datensatz anlegen. Schöner wäre, wenn der gerade erzeugte Datensatz aktiv bleibt. Dazu merkt man sich einfach die aktive Row und setzt die danach wieder. Das Ganze habe ich auf einen Button gelegt, mit dem der Anwender die Eingabe abschließt. Alternativ kann natürlich auch ein anderes Event dafür verwendet werden. Sinnvoll ist hier sicher auch das BeforeSelectChange-Event des Grids entsprechend einzubinden. private void button1_Click(object sender, EventArgs e) { this.myBindingSource.EndEdit(); UltraGridRow temp = this.ultraGrid1.ActiveRow; this.ultraGrid1.ActiveRow = this.ultraGrid1.Rows.TemplateAddRow; this.ultraGrid1.ActiveRow = temp; }
Bin da gerade auf ein nettes Angebot gestoßen. SitePal bietet animierte Figuren, die man sich selber konfigurieren kann. Diese Figuren können dann Texte, die man vorgibt, sprechen, entweder aufgezeichnete Audio-Files oder auch über Texteingabe. Diese Figuren lassen sich dann in eine Web-Seite integrieren. Ist sicher interessant, wenn man ein entsprechendes Projekt hat. Mal sehen, vielleicht kann ich das zukünftig mal nutzen. SitePal Homepage
Bei der Neuinstallation eines Entwicklungsrechners dauert es immer ewig, bis man sich die ganzen nützlichen Helferlein zusammengesucht hat, die man im Laufe der Zeit zu schätzen gelernt hat. Deshalb habe ich mir gedacht, ich schreibe mir mal eine Liste, die ich dann immer wieder ergänzen kann. Und vielleicht ist für den einen oder anderen da auch noch was interessantes dabei. Also so könnte ein Entwicklungsrechner aussehen:
Betriebsystem / Standardanwendungen:
- Windows Vista Ultimate
- Office 2007
- SQL-Server 2005 Express
- SQL-Server 2005 Management Studio
Entwicklungsumgebung:
Tools
Stand 05.10.2007 To be continued...
Will man mit Visual Studio 2008 einem UserControl ein StyleSheet zuordnen, sieht das normalerweise so aus:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs"
Inherits="Controls.WebUserControl1" %>
<head>
<link href="../App_Themes/style.css" rel="stylesheet" type="text/css" />
</head>
<asp:Button ID="Button1" runat="server" Text="Button" /><asp:TextBox ID="TextBox1"
runat="server"></asp:TextBox>
Damit scheint aber der Designer von Visual Studio 2008 Beta 2 Probleme zu haben. Sowohl im Split-View als auch im Design-View wird nach einem Refresh nichts mehr angezeigt.

Bei mir hat hier ein kleiner Trick geholfen. Man verschiebt einfach das Link-Tag mit dem Stylesheet ganz an's Ende des Codes. Dann kann man mit Hilfe der Refresh-Funktion aus dem Kontext-Menü des Design-Panes die Anzeige aktualisieren und siehe da, Button und Textbox werden wieder angezeigt.

Ist eine ASP.Net Seite scrollbar, gibt es den unschönen Effekt, dass die Seite nach dem Postback wieder ganz oben steht. Ich habe mal eine kleine Beispiel-Seite, die diesen Effekt demonstriert:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm2.aspx.cs" Inherits="ValuePlanner_2008.Web.INN_ProjectArea.WebForm2" %> <%@ Register Assembly="cTextBox" Namespace="artiso_lib.UserControls" TagPrefix="cc1" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page </title> </head> <body> <form id="form1" runat="server"> <div> <% 1: for (int i = 0; i < 50; i++) 2: { 3: Response.Write(i + "<br>"); 4: } 5: %> </div> </form> </body> </html>
Dieses Problem kann man ganz einfach umgehen, indem man in der Page-Direktive das Attribut MaintainScrollPositionOnPostback="true" einfügt, also:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm2.aspx.cs" Inherits="ValuePlanner_2008.Web.INN_ProjectArea.WebForm2" MaintainScrollPositionOnPostback="true" %>
Im ASP.Net AJAX Toolkit findet man ein ModalPopup, das sehr gute Dienste leistet, will man Meldungen etwas eleganter ausgeben als mit einem schlichten alert(). Die Handhabung ist relativ einfach. Man nehme ein beliebiges Serverseitiges Control und weise diesem den ModalPopupExtender zu. Dann gibt man noch mindestens die ID des Controls an, das den Inhalt des Popups repräsentieren soll und fertig. Will man das Popup allerdings client-seitig per Java-Script aufrufen, muss man ein wenig in die Trickkiste greifen. Das Problem dabei ist, dass der ModalPopupExtender immer ein TargetControl braucht. Ich habe dafür einfach ein unsichtbares DIV erzeugt, das mit runat="server" auch auf dem Server sichtbar gemacht wurde. Dann kann man mit $Find('<ID des ModalPopupExtenders>').show() das Popup aufrufen.
Neu war an diesem Beispiel für mich auch der Style filter: alpha(opacity = 70) mit dem man eine Transparenz erzeugen kann.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="ValuePlanner_2008.Web.INN_ProjectArea.WebForm1" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
<style type="text/css">
.modalBackground
{
filter: alpha(opacity = 70);
background-color: #CCCCCC;
}
.ErrorMessage
{
background-color: #800000;
color: #FFFFFF;
}
</style>
</head>
<script language="javascript" type="text/javascript"> 1: 2: function okScript() 3: { 4: alert ("OK-Button wurde gedrückt"); 5: } </script>
<body>
<form id="form1" runat="server">
<div id="PopupPseudoPanel" style="display: none" runat="server">
</div>
<cc1:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
</cc1:ToolkitScriptManager>
<cc1:ModalPopupExtender ID="ModalPopupExtender" runat="server" PopupControlID="Panel1"
DynamicServicePath="" Enabled="True" TargetControlID="PopupPseudoPanel" BackgroundCssClass="modalBackground"
OkControlID="OkButton" CancelControlID="CancelButton" OnOkScript="okScript();">
</cc1:ModalPopupExtender>
<br />
<br />
<input id="Button2" type="button" value="button" onclick="$find('ModalPopupExtender').show();" />
<asp:Panel ID="Panel1" runat="server" CssClass="ErrorMessage" style="display:none">
<h1>
Popup</h1>
<p>
Das ist ein Popup!</p>
<center>
<asp:Button ID="OkButton" runat="server" Text="OK"></asp:Button>
<asp:Button ID="CancelButton" runat="server" Text="Cancel"></asp:Button>
</center>
</asp:Panel>
</form>
</body>
</html>
Wie man an meinen letzten Posts sieht, beschäftige ich mich momentan sehr intensiv mit LINQ to SQL. Eine wichtige Lektion habe ich gerade eben gelernt. Folgende Ausgangssituation: Ich habe eine ASP.Net Anwendung. In einem DAL lese ich per LINQ ein Objekt aus der Datenbank und schreibe das dann in eine Session-Variable. Der Anwender bekommt das Objekt an der Oberfläche angezeigt und kann es verändern. Nun möchte ich das Objekt wieder in die Datenbank zurückschreiben. Fast alle Tutorials zu LINQ beschreiben, wie einfach es ist, Daten via LINQ zu schreiben. Aber dort wird in einer einzigen Funktion erst gelesen, dann werden die Daten verändert und dann wieder zurückgeschrieben. Das tritt ja aber in der Praxis eher selten auf. Also habe ich mich gefragt, was LINQ tut, wenn ich den DataContext einfach neu instanziere und dann einfach mein Objekt per Add an die Tabelle hinzufüge. Na ja,wie zu erwarten, hat LINQ gemeldet, dass der entsprechende Primary Key bereits existiert. Also was tun? Den gesamten DataContext in die Session legen? Dass das keine gute Idee ist, habe ich ziemlich schnell rausgefunden. Der DataContext skaliert sehr schlecht, da er ein eingebautes Change-Tracking, Identity Tracking und sonst noch ein paar Nettigkeiten hat, die sich aber in einer Web_Anwendung recht eklig verhalten können. Nach einigem Suchen bin ich dann drauf gekommen, dass es auch noch eine Attach-Methode gibt. Damit konnte ich mein Problem schön lösen. Das Ergebnis für das Schreiben sieht nun so aus: | |