DNN-Modulprogrammierung - ein einfaches Beispiel (Schritt 5a - Die Präsentationsschicht - Das Edit-Control in C#) (Michael Tobisch)
Das Edit-Control - C#
Legen wir im Ordner DesktopModules/DnnUgDe ein neues Element an:
Wählen wir Web-Benutzersteuerelement, stellen wir als Sprache Visual C# ein, geben den Namen ContactsEdit.ascx an und achten wir darauf, dass das Kontrollkästchen Code in eigener Datei platzieren aktiviert ist. Dann klicken wir auf Hinzufügen.
Zunächst einmal öffnen wir auch unsere Code-Datei (ContactsEdit.ascx.cs - diese verbirgt sich eventuell hinter dem +-Zeichen links neben der soeben angelegten Datei):
Packen wir unsere partielle Klasse zunächst wieder in einen Namespace, und leiten wir sie nicht wie vorgegeben von der Klasse UserControl, sondern von der Klasse PortalModuleBase aus dem Namespace DotNetNuke.Entities.Modules ab. Nennen wir die partielle Klasse ContactsEdit. Unser Code sieht nun wie folgt aus:
namespace DnnUgDe.DNN.Modules.CS.Contacts
{
public partial class ContactsEdit : PortalModuleBase
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
Im Benutzersteuerelement selbst (ContactsView.ascx) müssen wir nun noch das Inherits-Attribut unseren Änderungen anpassen. Öffnen wir die Datei in der Quellcode-Ansicht, und weisem dem Attribut den Wert "DnnUgDe.DNN.Modules.CS.Contacts.ContactsEdit" zu:
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="ContactsEdit.ascx.cs"
Inherits="DnnUgDe.DNN.Modules.CS.Contacts.ContactsEdit" %>
Wechseln wir nun in die Entwurfs-Ansicht. Ziehen wir dann aus der Toolbox ein FormView-Steuerelement auf den Entwurf. Mit Hilfe des FormView werden wir ein Formular für einzelne Datensätze anzeigen. Im Eigenschaften-Fenster geben wir dem Steuerelement die ID ContactsForm:
Öffnen wir nun das Smart-Tag des Forms und wählen wir aus der Liste Datenquelle auswählen die Option Neue Datenquelle...:
Im Assistenten, der nun erscheint, wählen wir einmal Objekt als Datenquellentyp aus (die notwendigen Datenobjekte haben wir ja in Schritt 3 erstellt), und nennen die Datenquelle ContactsDataSource:
Aktivieren wir die Checkbox Nur Datenkomponenten anzeigen und wählen wir aus der Liste unser Objekt DnnUgDe.DNN.Modules.CS.Contacts.Business.ContactController:
Im folgenden Schritt geben wir nun die Methoden für die Auswahl eines einzelnen Datensatzes, die Aktualisierung, das Einfügen und das Löschen eines Datensatzes an, die unser Objekt zur Verfügung stellt. Die Methode zur Auswahl heisst GetContact(Int32 contactID), wählen wir also diese aus der Liste der im Register SELECT aus:
Klicken wir dann auf das Register UPDATE und wählen die Methode ChangeContact(Int32 contactID, String firstName, String lastName, String emailAddress) aus:
Wechseln wir in das Register INSERT und wählen die Methode AddContact(Int32 moduleID, String firstName, String lastName, String emailAddress):
Zuletzt wechseln wir in das Register DELETE und wählen dort die Methode DropContact(Int32 contactID):
Klicken wir danach auf Weiter. Die Auswahlmethode erfordert einen Parameter (contactID). Wir wissen aber zum derzeitigen Zeitpunkt noch nicht, ob wir (zur Laufzeit) das Steuerelement verwenden, um einen neuen Datensatz anzulegen oder einen bestehenden zu bearbeiten - verwenden wollen wir es für beides. Die Lösung für dieses "Problem" ist: wir übergeben dem Steuerelment die contactID als Parameter, wenn wir einen Datensatz bearbeiten wollen, und nichts, wenn wir einen neuen Datensatz anlegen wollen. Entsprechend weisen wir dem FormView den Bearbeitungsmodus (DefaultMode - Edit oder Insert) zu, wenn das Steuerelement geladen wird - zu diesem Zeitpunkt wissen wir dann ja, ob ein Parameter übergeben wurde oder nicht. Wurde einer übergeben, weisen wir diesen der Datenquelle zur Laufzeit zu.
Hier lassen wir Parameterquelle also auf dem Wert None und klicken auf Fertig stellen.
Wechseln wir in die Quellansicht und passen wir unser Form-Element ein bisschen an. Ein FormView verwendet Templates, um das Formular für verschiedene Modi (Ansicht, Bearbeiten, Neu anlegen) aufzubereiten. Was wir hier nicht benötigen ist das Template für die Ansicht. Wir wollen entweder ein neues Element anlegen (dazu benötigen wir das InsertItemTemplate) oder ein Element bearbeiten oder löschen (das erledigen wir mit dem EditItemTemplate). Wir löschen also einmal das Template zur Ansicht, indem wir alles von <ItemTemplate> bis </ItemTemplate> löschen.
Der Designer zeigt alle Eigenschaften unseres Objekts an, einige davon (ModuleID, ContactID und KeyID) interessieren hier aber nicht. Wir löschen also auch die Beschriftungen und dazugehörigen Textboxen aus den verbleibenden Templates. Der Quellcode für das FormView sollte nun etwa so aussehen:
<asp:FormView ID="ContactsForm" runat="server"
DataSourceID="ContactsDataSource">
<EditItemTemplate>
Firstname:
<asp:TextBox ID="FirstnameTextBox" runat="server"
Text='<%# Bind("Firstname") %>' />
<br />
Lastname:
<asp:TextBox ID="LastnameTextBox" runat="server"
Text='<%# Bind("Lastname") %>' />
<br />
EmailAddress:
<asp:TextBox ID="EmailAddressTextBox" runat="server"
Text='<%# Bind("EmailAddress") %>' />
<br />
<asp:LinkButton ID="UpdateButton" runat="server" CausesValidation="True"
CommandName="Update" Text="Aktualisieren" />
<asp:LinkButton ID="UpdateCancelButton" runat="server"
CausesValidation="False" CommandName="Cancel" Text="Abbrechen" />
</EditItemTemplate>
<InsertItemTemplate>
Firstname:
<asp:TextBox ID="FirstnameTextBox" runat="server"
Text='<%# Bind("Firstname") %>' />
<br />
Lastname:
<asp:TextBox ID="LastnameTextBox" runat="server"
Text='<%# Bind("Lastname") %>' />
<br />
EmailAddress:
<asp:TextBox ID="EmailAddressTextBox" runat="server"
Text='<%# Bind("EmailAddress") %>' />
<br />
<asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True"
CommandName="Insert" Text="Einfügen" />
<asp:LinkButton ID="InsertCancelButton" runat="server"
CausesValidation="False" CommandName="Cancel" Text="Abbrechen" />
</InsertItemTemplate>
</asp:FormView>
Nachdem wir das Schlüsselfeld zu unserem Datensatz nun aus den Templates entfern haben, müssen wir dieses in den Attributen des FormViews definieren. Dies geschieht durch hinzufügen des Attributs DataKeyNames. Weisen wir unseren Schlüssel ContactID dem Attribut als Wert zu.
Ausserdem wollen wir das Formular in eine Tabelle verpacken (weil das einfach hübscher aussieht) und die Link-Buttons unseren Bedürfnissen anpassen (im Bearbeitungsmodus soll ja noch der Button zum Löschen dazukommen, der normalerweise im Ansichts-Template angeboten wird).
Die Feldbeschriftungen wollen wir durch Label-Steuerelemente ersetzen - schließlich wollen wir unser Formular auch lokalisieren, und in der deutschen Version soll dann nicht "Lastname", sondern "Nachname" stehen.
Nach unseren Änderungen erhalten wir folgenden Quelltext für unser FormView:
<asp:FormView ID="ContactsForm" runat="server"
DataSourceID="ContactsDataSource" DataKeyNames="ContactID">
<EditItemTemplate>
<table border="0" cellpadding="4" cellspacing="0" width="100%">
<tr>
<td><asp:Label ID="FirstnameLabel" runat="server" /></td>
<td>
<asp:TextBox ID="FirstnameTextBox" runat="server" Text='<%# Bind("Firstname") %>' />
</td>
</tr>
<tr>
<td><asp:Label ID="LastnameLabel" runat="server" /></td>
<td>
<asp:TextBox ID="LastnameTextBox" runat="server" Text='<%# Bind("Lastname") %>' />
</td>
</tr>
<tr>
<td><asp:Label ID="EmailAddressLabel" runat="server" /></td>
<td>
<asp:TextBox ID="EmailAddressTextBox" runat="server" Text='<%# Bind("EmailAddress") %>' />
</td>
</tr>
</table>
<br />
<asp:LinkButton ID="UpdateButton" runat="server" CausesValidation="True"
CommandName="Update" Text="Aktualisieren" />
<asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False"
CommandName="Delete" Text="Löschen" />
<asp:LinkButton ID="UpdateCancelButton" runat="server"
CausesValidation="False" CommandName="Cancel" Text="Abbrechen" />
</EditItemTemplate>
<InsertItemTemplate>
<table border="0" cellpadding="4" cellspacing="0" width="100%">
<tr>
<td><asp:Label ID="FirstnameLabel" runat="server" /></td>
<td>
<asp:TextBox ID="FirstnameTextBox" runat="server" Text='<%# Bind("Firstname") %>' />
</td>
</tr>
<tr>
<td><asp:Label ID="LastnameLabel" runat="server" /></td>
<td>
<asp:TextBox ID="LastnameTextBox" runat="server" Text='<%# Bind("Lastname") %>' />
</td>
</tr>
<tr>
<td><asp:Label ID="EmailAddressLabel" runat="server" /></td>
<td>
<asp:TextBox ID="EmailAddressTextBox" runat="server" Text='<%# Bind("EmailAddress") %>' />
</td>
</tr>
</table>
<br />
<asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True"
CommandName="Insert" Text="Einfügen" />
<asp:LinkButton ID="InsertCancelButton" runat="server"
CausesValidation="False" CommandName="Cancel" Text="Abbrechen" />
</InsertItemTemplate>
</asp:FormView>
Wie schon im vorangegangenen Tutorial (4a) entfernen wir hier auch noch das Attibut OldValuesParameterFormatString aus dem Datenquellen-Control. Dieses Attribut ist bei der Verwendung von optimistischer Konkurrenz notwendig. Darauf verzichten wir hier einmal, und wenn wir es so stehen lassen, wie es uns der Designer reinschreibt, dann kommt es zu einer Fehlermeldung zur Laufzeit. Den Rest der Datenquelle lassen wir unberührt.
Speichern wir nun die Datei ContactsEdit.ascx, und öffnen wir die Datei ContactsEdit.ascx.cs. Zunächst einmal müssen wir im Ereignis Page_Load feststellen, ob ein Parameter übergeben wurde oder nicht. Je nachdem, ob wir jetzt einen neuen Datensatz einfügen oder einen bestehenden Datensatz bearbeiten oder löschen wollen, müssen wir den Modus des FormView entsprechend setzen und in der Datenquelle den jeweils notwendigen Parameter zuweisen (das ist beim Einfügen die ModuleID, bei allen anderen Methoden die ContactID).
protected void Page_Load(object sender, EventArgs e)
{
try
{
if (Request.Params["ContactID"] == null)
{
// Kein Parameter übergeben - neuer Datensatz!
this.ContactsForm.DefaultMode = FormViewMode.Insert;
// ModulID dem Parameter zuweisen!
this.ContactsDataSource.InsertParameters["moduleID"].DefaultValue =
base.ModuleId.ToString();
}
else
{
this.ContactsForm.DefaultMode = FormViewMode.Edit;
this.ContactsDataSource.SelectParameters["contactID"].DefaultValue =
Request.Params["ContactID"];
}
}
catch (Exception ex)
{
Exceptions.ProcessModuleLoadException(this, ex);
}
}
Anmerkung: Der Parameter ContactID wird aus dem bereits erstellten Steuerelement ContactsView übergeben - dort haben wir das noch nicht implementiert, dazu kommen wir aber später.
Um die Beschriftungen in unserem Steuerelement lokalisiert anzeigen zu können müssen wir noch eine Resourcendatei im Ordner App_LocalResources (unterhalb unseres DesktopModules/DnnUgDe_Contacts) anlegen. Die Resourcendatei muss ContactsEdit.ascx.resx heissen. Fügen wir folgende Resourcen ein:
| Name |
Wert |
Kommentar |
| FirstnameLabel.Text |
First name: |
|
| LastnameLabel.Text |
Last name: |
|
| EmailAddressLabel.Text |
Email address: |
|
Dann fügen wir noch eine deutsche Datei hinzu (ContactsEdit.ascx.de-AT.resx, ContactsEdit.ascx.de-CH.resx oder ContactsEdit.ascx.de-DE.resx - oder alle drei) mit folgendem Inhalt:
| Name |
Wert |
Kommentar |
| FirstnameLabel.Text |
Vorname: |
|
| LastnameLabel.Text |
Nachname: |
|
| EmailAddressLabel.Text |
E-Mail-Adresse: |
|
Um unseren Labels diese zuzuweisen benötigen wir ein Ereignis, in dem diese tatsächlich (und ohne lange Suche) zur Verfügung stehen. Dazu bietet sich das ItemCreated-Ereignis des FormViews an, weil hier das entsprechende Template bereits geladen wurde. Wir müssen also nur nach den Labels suchen, und diesen die entsprechenden Texte zuweisen. In der Datei ContactsEdit.ascx wechseln wir in die Entwurfsansicht - keine Bange, wenn das Formular hier nicht mehr richtig angezeigt wird. Wir haben ja das ItemTemplate entfernt (weil wir es nicht benötigen) - und damit kann der Designer das Formular nicht mehr darstellen. Um es wieder anzuzeigen, kann man in den Eigenschaften die Eigenschaft DefaultMode auf Insert oder Edit setzen - notwendig ist das aber nicht. Wir markieren das FormView-Element und wechseln dann im Eigenschaften- Fenster auf die Ereignisse (durch klicken des Symbols mit dem gelben Blitz oben:
Wenn wir nun in das (leere) Feld in der Zeile ItemCreated doppelklicken, so legt uns der Designer automatisch eine Methode im dahinterstehenden Code (unserer Datei ContactsView.ascx.cs) an und weist diese dem Ereignis zu. Unser Quellcode sieht danach folgendermaßen aus:
<asp:FormView ID="ContactsForm" runat="server"
DataSourceID="ContactsDataSource" DataKeyNames="ContactID"
onitemcreated="ContactsForm_ItemCreated">
Schließlich finden wir im dahinterstehenden Code die Methode ContactsForm_ItemCreated bereits mit den notwendigen Parametern angelegt:
protected void ContactsForm_ItemCreated(object sender, EventArgs e)
{
}
Wir suchen nun die Labels in unserem FormView und verwenden die Methode Localization.GetString aus dem Namespace DotNetNuke.Services.Localization, um die Texte aus unserer lokalen Resourcendatei zuzuweisen:
protected void ContactsForm_ItemCreated(object sender, EventArgs e)
{
FormView contactsForm = (FormView)sender;
// Labels finden
Label firstnameLabel = (Label)contactsForm.FindControl("FirstnameLabel");
Label lastnameLabel = (Label)contactsForm.FindControl("LastnameLabel");
Label emailAddressLabel = (Label)contactsForm.FindControl("EmailAddressLabel");
// Texte zuweisen
firstnameLabel.Text = Localization.GetString("FirstnameLabel.Text", LocalResourceFile);
lastnameLabel.Text = Localization.GetString("LastnameLabel.Text", LocalResourceFile);
emailAddressLabel.Text = Localization.GetString("EmailAddressLabel.Text", LocalResourceFile);
}
Je nachdem, ob sich das Formular nun im Insert- oder Edit-Modus befindet haben wir noch unterschiedliche Link-Buttons zum Speichern, Löschen und Abbrechen. Die Texte für diese Buttons weisen wir aber nicht aus der lokalen Resourcen-Datei zu, sondern verwenden eine Resourcendatei, die sich im Verzeichnis App_GlobalResources unserer DotNetNuke-Installation befindet. Es gehört zu den guten Praktiken, diese Datei so oft als möglich einzusetzen, um ein einheitliches Erscheinungsbild zu erreichen (z.B. soll ja ein Button, der zum Speichern dient, nicht in einem Modul mit Speichern und in einem anderen Modul mit Aktualisieren beschriftet sein. Die Verwendung der zentralen Resourcen hilft uns, dies ohne langes Nachdenken zu bewerkstelligen.
In der Datei SharedResources[.Sprache-Kultur].resx finden sich die Namen cmdCancel.Text, cmdDelete.Text und cmdUpdate.Text, die wir hier verwenden wollen. Wir fügen also am Ende unserer Methode noch folgenden Code ein:
if (contactsForm.DefaultMode == FormViewMode.Insert)
{
// Formular ist im Einfüge-Modus
// Link-Buttons finden
LinkButton insertButton = (LinkButton)contactsForm.FindControl("InsertButton");
LinkButton insertCancelButton = (LinkButton)contactsForm.FindControl("InsertCancelButton");
// Texte zuweisen
insertButton.Text = Localization.GetString("cmdUpdate.Text");
insertCancelButton.Text = Localization.GetString("cmdCancel.Text");
}
else
{
// Formular ist im Bearbeitungs-Modus
// Link-Buttons finden
LinkButton updateButton = (LinkButton)contactsForm.FindControl("UpdateButton");
LinkButton deleteButton = (LinkButton)contactsForm.FindControl("DeleteButton");
LinkButton updateCancelButton = (LinkButton)contactsForm.FindControl("UpdateCancelButton");
// Texte zuweisen
updateButton.Text = Localization.GetString("cmdUpdate.Text");
deleteButton.Text = Localization.GetString("cmdDelete.Text");
updateCancelButton.Text = Localization.GetString("cmdCancel.Text");
}
Fügen wir unseren lokalen Resourcendateien noch einen Text für den Modultitel bei. Diesen mussen wir nicht auslesen und programmatisch zuweisen, das erledigt das DotNetNuke-Framework für uns, wenn der Name des Eintrags ControlTitle_edit.Text lautet. Legen wir diesen also an und weisen ihm den Wert Add/Edit contact (in der Datei ContactEdit.resx) bzw. Kontakt anlegen/bearbeiten (in den deutschen Lokalisierungen) zu.
Wir wollen nun noch folgendes erreichen: Wenn auf einen der Link-Buttons geklickt wird, dann soll die gewünschte Aktion durchgeführt und zum Haupt-Steuerelement (ContactsView) zurückgegangen werden. Um allfällige Eingabefehler (Validierungen) werden wir uns in einem späteren Tutorial kümmern.
Einfach ist das bei der Handhabung der beiden Cancel-Buttons. Wir schreiben einfach einen Click-Handler, der zum Ansichts-Steuerelement weiterleitet:
protected void Cancel_Click(object sender, EventArgs e)
{
try
{
// Leite zum Haupt-Steuerelement um
Response.Redirect(Globals.NavigateURL(), true);
}
catch (Exception ex)
{
Exceptions.ProcessModuleLoadException(this, ex);
}
}
Diesen Handler weisen wir beiden Abbrechen-Links (UpdateCancelButton und InsertCancelButton im Click-Ereignis zu. Dazu können wir entweder einfach im Quellcode der Seite ContactsEdit.ascx das Attribut OnClick mit dem Wert Cancel_Click belegen, was folgendermaßen aussehen würde:
...
<asp:LinkButton ID="UpdateCancelButton" runat="server"
CausesValidation="False" CommandName="Cancel" Text="Abbrechen" OnClick="Cancel_Click" />
...
<asp:LinkButton ID="InsertCancelButton" runat="server"
CausesValidation="False" CommandName="Cancel" Text="Abbrechen" OnClick="Cancel_Click" />
Wir können das aber auch über den Designer bewerkstelligen. Wählen wir dazu im Entwurfsmodus aus dem Smart-Tag des FormViews Vorlagen bearbeiten:
Im Modus Vorlagen bearbeiten wählen wir aus der Liste Anzeige den Eintrag EditItemTemplate aus. Markieren wir hier unseren LinkButton Abbrechen:
Wenn wir nun in den Eigenschaften auf die Ereignisansicht wechseln (Symbol mit dem gelben Blitz), so können wir aus der Auswahlliste neben der Aktion Click die soeben erstellte Methode auswählen und damit dem Event zuweisen.
Äquivalent können wir mit dem InsertItemTemplate verfahren.
Wir wären geneigt, das gleiche Verfahren für die restlichen drei LinkButtons durchzuführen, aber in diesem Fall müssten wir uns im Code um das Speichern bzw. Löschen kümmern. Wir können auch anders vorgehen. Wenn wir die Events der Objektdatenquelle untersuchen, so stellen wir fest, dass es hier drei Ereignisse gibt, die nach dem Einfügen, Aktualisieren bzw. Löschen auftreten. Warum also nicht hier dir Weiterleitung auf das Anzeige-Steuerelement veranlassen? Wir werden gleich sehen, dass sich daraus noch ein ganz anderer Vorteil ergibt.
Legen wir also die entsprechenden Events durch doppelklicken in das leere Feld neben diesen Ereignissen einmal an. Im Code fügen wir jeweils die Weiterleitung auf das Ansichts-Steuerelement ein:
protected void ContactsDataSource_Deleted(object sender, ObjectDataSourceStatusEventArgs e)
{
// Leite zum Haupt-Steuerelement um
Response.Redirect(Globals.NavigateURL(), true);
}
protected void ContactsDataSource_Inserted(object sender, ObjectDataSourceStatusEventArgs e)
{
// Leite zum Haupt-Steuerelement um
Response.Redirect(Globals.NavigateURL(), true);
}
protected void ContactsDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
// Leite zum Haupt-Steuerelement um
Response.Redirect(Globals.NavigateURL(), true);
}
Der Vorteil dieser Methode ist aber folgender: Tritt irgendwo in unserer Geschäftslogik oder auf Ebende des Datenproviders oder gar in der Datenbank ein Fehler auf, so können wir diesen hier noch abfangen und in unserem Steuerelement bleiben, ohne dass der Benutzer die etwas kryptische Fehlermeldung angezeigt bekommt und dann eigentlich nicht mehr weiß, wie er weitermachen soll (z.B. wenn Pflichtfelder nicht eingegeben wurden):
Um eine benutzerfreundlichere Fehlermeldung anzuzeigen fügen wir dem Steuerelement ein Label hinzu, geben diesem die ID ErrorLabel, setzen die Vordergrundfarbe auf Rot und den Font auf Fett. Das Label soll nicht beim Laden der Seite angezeigt werden, also setzen wir die Eigenschaft Visible auf False, ausserdem - um bei einem neuerlichen Laden nach einem Postback der Seite das Label nicht mehr anzuzeigen, wenn davor ein Fehler passiert ist - auch die Eigenschaft EnableViewState auf False:
Tritt nun ein Fehler auf, zeigen wir das Label an, und setzen die Text-Eigenscahft des Labels auf eine benutzerfreundliche Fehlermeldung. Dazu legen wir in unseren lokalen Resource-Dateien neue Einträge an:
| Name |
Wert |
Kommentar |
| ErrorLabel.Delete.Text |
There was a problem deleting the contact. |
|
| ErrorLabel.Insert.Text |
There was a problem inserting the contact. |
|
| ErrorLabel.Update.Text |
There was a problem updating the contact. |
|
bzw.
| Name |
Wert |
Kommentar |
| ErrorLabel.Delete.Text |
Beim Anlegen des Kontakts ist ein Fehler aufgetreten. |
|
| ErrorLabel.Insert.Text |
Beim Akualisieren des Kontakts ist ein Fehler aufgetreten. |
|
| ErrorLabel.Update.Text |
Beim Löschen des Kontakts ist ein Fehler aufgetreten. |
|
Nun müssen wir nur noch in unseren Ereignissen abfragen, ob ein Fehler passiert ist, gegebenfalls die entsprechende Fehlermeldung anzeigen und auf der Seite verbleiben:
protected void ContactsDataSource_Deleted(object sender, ObjectDataSourceStatusEventArgs e)
{
if (e.Exception == null)
{
// Leite zum Haupt-Steuerelement um
Response.Redirect(Globals.NavigateURL(), true);
}
else
{
this.ErrorLabel.Text = Localization.GetString("ErrorLabel.Delete.Text", LocalResourceFile);
this.ErrorLabel.Visible = true;
e.ExceptionHandled = true;
}
}
protected void ContactsDataSource_Inserted(object sender, ObjectDataSourceStatusEventArgs e)
{
if (e.Exception == null)
{
// Leite zum Haupt-Steuerelement um
Response.Redirect(Globals.NavigateURL(), true);
}
else
{
this.ErrorLabel.Text = Localization.GetString("ErrorLabel.Insert.Text", LocalResourceFile);
this.ErrorLabel.Visible = true;
e.ExceptionHandled = true;
}
}
protected void ContactsDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
if (e.Exception == null)
{
// Leite zum Haupt-Steuerelement um
Response.Redirect(Globals.NavigateURL(), true);
}
else
{
this.ErrorLabel.Text = Localization.GetString("ErrorLabel.Update.Text", LocalResourceFile);
this.ErrorLabel.Visible = true;
e.ExceptionHandled = true;
}
}
Steuerelement der Moduldefinition zufügen
Um das Steuerelement der Moduldefinition zuzufügen navigieren wir im Browser zu unserem Portal, und wählen dort System :: Module aus dem Menü. Klicken wir in der Liste der Moduldefinitionen auf den kleinen Bleistift links in der Zeile, wo unser Modul angezeigt wird, um die Moduldefinition zu bearbeiten. Ganz unten klicken wir dann auf den Link Steuerelement hinzufügen:
In der folgenden Maske geben wir folgende Werte ein:
- Schlüssel: Edit
- Titel: Add/Edit contacts
- Quelle: DesktopModules/DnnUgDe_Contacts/ContactsEdit.ascx
- Typ: bearbeiten
Klicken wir dann auf Speichern.
ContactsView anpassen
Kontakte hinzufügen
Um aus unserem ContactsView-Steuerelement einen neuen Datensatz anlegen zu können, mussen wir in diesem die Schnittstelle IActionable implementieren. Dazu öffnen wir zunächst die Datei ContactsView.ascx.cs, und geben der Klasse an, dass IActionable implementiert wird:
public partial class ContactsView : PortalModuleBase, IActionable
Die Schnittstelle selbst wird durch die Eigenschaft ModuleActions implementiert. Diese gibt eine Sammlung von Modulaktionen (ModuleActionCollection) zurück, welche im Modulmenü angezeigt werden. Einige Modulaktionen sind ja bereits vorhanden (Moduleinstellungen oder Modul löschen z.B.). Wir müssen also dieser bereits vorhandenen Sammlung die Aktion "Neuen Kontakt anlegen" hinzufügen. Dies geschieht mit folgendem Code:
#region Optional Interfaces
public ModuleActionCollection ModuleActions
{
get
{
ModuleActionCollection actions = new ModuleActionCollection();
actions.Add(GetNextActionID(), Localization.GetString(ModuleActionType.AddContent, LocalResourceFile),
ModuleActionType.AddContent, String.Empty, String.Empty, EditUrl(), false, SecurityAccessLevel.Edit,
true, false);
return actions;
}
}
#endregion
Erwähnenswert ist hier der Parameter SecurityAccessLevel.Edit: Nur Benutzer, die Bearbeitungsrechte auf Modulebene haben, bekommen die Aktion angezeigt.
Um auch einen entsprechenden (lokalisierten) Text angezeigt zu bekommen müssen wir in der Resourcendatei ContactsView.resx (bzw. ContactsView.[de-AT|de-CH|de-DE].resx) noch einen Eintrag mit dem Namen AddContent.Action und dem Wert Add new contact (bzw. Neuen Kontakt anlegen) hinzufügen.
Kontakte bearbeiten
Wenn einmal Datensätze angelegt sind, dann sollen diese auch bearbeitet werden können. Wir legen also eine zusätzliche Spalte in unserem GridView an, welches unser Bearbeitungs-Steuerelement mit dem erforderlichen Parameter aufruft. Öffnen wir also die Datei ContactsView.asx im Entwurfs-Modus, und markieren wir das GridView. Aus dem Smart-Tag wählen wir Neue Spalte hinzufügen…:
Als Feldtyp wählen wir TemplateField, den Headertext lassen wir leer:
Klicken wir nun im Smart-Tag des GridViews auf Vorlagen bearbeiten. Nachdem wir sonst hier keine Template-Felder verwenden wird uns gleich die richtige Vorlage (das ItemTemplate der neuen Spalte) angezeigt. Fügen wir diesem einen ImageButton hinzu, dem wir die ID EditButton geben, und dem wir die ImageUrl ~//Portals/0/images/Articles/edit.gif (das ist der kleine Bleistift, der in den meisten DotNetNuke-Modulen angezeigt wird, wenn es gilt, irgendetwas zu bearbeiten) zuweisen. Die Eigenschaft CausesValidation setzen wir auf False, da wir hier ja keine Eingaben zu überprüfen haben.
Klicken wir nun im Smart-Tag des ImageButtons auf DataBindings bearbeiten…:
Wir wollen als Argument die ContactID übergeben, also wählen wir im Dialog in der Liste Bindbare Eigenschaften die Eigenschaft CommandArgument aus und definieren die Bindung mit dem Codeausdruck Eval("ContactID"). Ich will mich hier nicht über Datenbindungsausdrücke verlieren, wem Bind() und Eval() nicht so geläufig sind, der möge in der MSDN-Dokumentation nachlesen.
Wir benötigen nur noch einen Eventhandler für das Click-Ereignis dieses Buttons. Dazu wechseln wir in den Eigenschaften des Buttons auf die Ereignisanzeige (den kleinen Blitz oben), und doppelklicken in das leere Feld neben Click. Fügen wir folgenden Code in das Click-Ereignis ein:
protected void EditButton_Click(object sender, ImageClickEventArgs e)
{
ImageButton pencil = (ImageButton)sender;
Response.Redirect(EditUrl("ContactID", pencil.CommandArgument));
}
Das Bild sollte nun auch noch mit einem Alternativtext hinterlegt werden. Dies können wir im Ereignis RowCreated des GridViews erledigen. Wie schon in den Beispielen vorher erzeugen wir dieses aus dem Designer, indem wir in den Ereignis-Eigenschaften des GridViews in das leere Feld neben RowCreated doppelklicken. Fügen wir folgenden Code ein:
protected void ContactsGrid_RowCreated(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
// Datenzeile! - nur hier gibt's den Bleistift!
ImageButton pencil = (ImageButton)e.Row.FindControl("EditButton");
pencil.AlternateText = Localization.GetString("cmdEdit.Text");
}
}
Weil wir ja den Bleistift in der Kopfzeile des Grids nicht finden, müssen wir abfragen, ob wir uns in einer Datenzeile befinden. Ist dies der Fall, so suchen wir unseren ImageButton in der soeben erzeugten Zeile und hinterlegen ihn mit dem in der Datei SharedResources[.Sprache-Kultur].resx hinterlegten Wert von cmdEdit.Text.
Was wir noch wollen ist, dass der Bleistift nur dann angezeigt wird, wenn der Benutzer überhaupt bearbeiten darf. Dazu blenden wir die letzte Spalte aus, wenn der Benutzer keine Bearbeitungsrechte hat. Dies geschieht im Ereignis PreRender des Grids - hier haben wir das gesamte Grid bereits aufgebaut, aber noch nicht gerendert, daher können wir ohne Probleme die letzte Spalte auf unsichtbar setzen:
protected void ContactsGrid_PreRender(object sender, EventArgs e)
{
GridView contactsGrid = (GridView)sender;
if (!(base.IsEditable))
contactsGrid.Columns[contactsGrid.Columns.Count - 1].Visible = false;
}
Schlussendlich entfernen wir die Spalten ModuleID, ContactID und KeyID aus dem Grid, verbreitern das Grid auf 100% und ordnen den Spaltenüberschriften noch lokalisierte Titel zu. Dazu legen wir in den Resource-Dateien (ContactsView.resx bzw. ContatcsView.[de-AT|de-CH|de-DE].resx) folgende Einträge an:
| Name |
Wert |
Kommentar |
| Firstname.HeaderText |
First name |
|
| Lastname.HeaderText |
Last name |
|
| EmailAddress.HeaderText |
Email address |
|
bzw.
| Name |
Wert |
Kommentar |
| Firstname.HeaderText |
Vorname |
|
| Lastname.HeaderText |
Nachname |
|
| EmailAddress.HeaderText |
E-Mail-Adresse |
|
Im Ereignis ContactsGrid_RowCreated (das gibt's schon) fügen wir noch folgenden Code ein:
if (e.Row.RowType == DataControlRowType.Header)
{
foreach (TableCell tc in e.Row.Cells)
tc.Text = Localization.GetString(tc.Text + ".HeaderText", LocalResourceFile);
}
Der Quelltext für das Grid sieht nach dem Löschen der drei Spalten und dem Verbreitern so aus:
<asp:GridView ID="ContactsGrid" runat="server"
DataSourceID="ContactsDataSource" AutoGenerateColumns="False"
onprerender="ContactsGrid_PreRender" onrowcreated="ContactsGrid_RowCreated"
Width="100%">
<Columns>
<asp:BoundField DataField="Firstname" HeaderText="Firstname"
SortExpression="Firstname" />
<asp:BoundField DataField="Lastname" HeaderText="Lastname"
SortExpression="Lastname" />
<asp:BoundField DataField="EmailAddress" HeaderText="EmailAddress"
SortExpression="EmailAddress" />
<asp:TemplateField>
<ItemTemplate>
<asp:ImageButton ID="EditButton" runat="server" CausesValidation="False"
ImageUrl="~//Portals/0/images/Articles/edit.gif" CommandArgument='<%# Eval("ContactID") %>'
onclick="EditButton_Click" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Ja - und damit wären wir eigentlich fertig. Das Modul kann jetzt getestet werden.
Source-Code downladen