Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
door Scott Mitchell
In eerdere zelfstudies hebben we gezien hoe het besturingselement GridView het eenvoudig maakt om tekstgegevens te bewerken en te verwijderen. In deze zelfstudie zien we hoe het besturingselement GridView het ook mogelijk maakt om binaire gegevens te bewerken en te verwijderen, ongeacht of die binaire gegevens worden opgeslagen in de database of worden opgeslagen in het bestandssysteem.
Introductie
In de afgelopen drie zelfstudies hebben we nogal wat functionaliteit toegevoegd voor het werken met binaire gegevens. We zijn begonnen door een BrochurePath kolom toe te voegen aan de Categories tabel en de architectuur dienovereenkomstig bij te werken. We hebben ook Data Access Layer- en Business Logic Layer-methoden toegevoegd om te werken met de bestaande Picture kolom van de tabel Categorieën, die de binaire inhoud van een afbeeldingsbestand bevat. We hebben webpagina's gemaakt om de binaire gegevens in een GridView te presenteren een downloadkoppeling voor de brochure, met de afbeelding van de categorie die in een <img> element wordt weergegeven en een DetailsView hebben toegevoegd zodat gebruikers een nieuwe categorie kunnen toevoegen en de brochure- en afbeeldingsgegevens kunnen uploaden.
Het enige wat nog moet worden geïmplementeerd, is de mogelijkheid om bestaande categorieën te bewerken en te verwijderen, wat we in deze zelfstudie doen met behulp van de ingebouwde functies voor bewerken en verwijderen van GridView. Wanneer een categorie wordt bewerkt, kan de gebruiker desgewenst een nieuwe afbeelding uploaden of de categorie de bestaande afbeelding blijven gebruiken. Voor de brochure kunnen ze ervoor kiezen om de bestaande brochure te gebruiken, een nieuwe brochure te uploaden of om aan te geven dat aan de categorie geen brochure meer is gekoppeld. Laten we beginnen!
Stap 1: De gegevenstoegangslaag bijwerken
De DAL heeft automatisch gegenereerd Insert, Updateen Delete methoden, maar deze methoden zijn gegenereerd op basis van de CategoriesTableAdapter hoofdquery, die niet de Picture kolom bevat. Daarom bevatten de Insert en Update methoden geen parameters voor het opgeven van de binaire gegevens voor de afbeelding van de categorie. Net als in de vorige zelfstudie moeten we een nieuwe TableAdapter-methode maken voor het bijwerken van de Categories tabel bij het opgeven van binaire gegevens.
Open de Getypte Gegevensset en klik in de Ontwerper met de rechtermuisknop op de CategoriesTableAdapter-koptekst en kies Query toevoegen in het contextmenu om de TableAdapter Queryconfiguratiewizard te starten. Deze wizard begint met het vragen hoe de TableAdapter-query toegang moet krijgen tot de database. Kies SQL-instructies gebruiken en klik op Volgende. In de volgende stap wordt gevraagd om het type query dat moet worden gegenereerd. Omdat we een query maken om een nieuwe record aan de Categories tabel toe te voegen, kiest u BIJWERKEN en klikt u op Volgende.
Afbeelding 1: Selecteer de Bijwerken-optie (klik om de volledige afbeelding te bekijken)
We moeten nu de UPDATE SQL-instructie opgeven. De wizard stelt automatisch een UPDATE instructie voor die overeenkomt met de hoofdquery van TableAdapter (een die de CategoryName, Descriptionen BrochurePath waarden bijwerkt). Wijzig de instructie zodat de Picture kolom samen met een @Picture parameter wordt opgenomen, zoals:
UPDATE [Categories] SET
[CategoryName] = @CategoryName,
[Description] = @Description,
[BrochurePath] = @BrochurePath ,
[Picture] = @Picture
WHERE (([CategoryID] = @Original_CategoryID))
In het laatste scherm van de wizard wordt ons gevraagd de nieuwe TableAdapter-methode een naam te geven. Voer de tekst in UpdateWithPicture en klik op Voltooien.
Afbeelding 2: Geef de methode UpdateWithPicture New TableAdapter een naam (klik om de afbeelding op volledige grootte weer te geven)
Stap 2: De methoden voor bedrijfslogicalagen toevoegen
Naast het bijwerken van de DAL moeten we de BLL bijwerken om methoden voor het bijwerken en verwijderen van een categorie op te nemen. Dit zijn de methoden die worden aangeroepen vanuit de presentatielaag.
Voor het verwijderen van een categorie kunnen we de CategoriesTableAdapter automatisch gegenereerde Delete methode gebruiken. Voeg de volgende methode toe aan de klasse CategoriesBLL:
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Delete, true)]
public bool DeleteCategory(int categoryID)
{
int rowsAffected = Adapter.Delete(categoryID);
// Return true if precisely one row was deleted, otherwise false
return rowsAffected == 1;
}
Voor deze zelfstudie maken we twee methoden voor het bijwerken van een categorie: één die de binaire afbeeldingsgegevens verwacht en de UpdateWithPicture methode aanroept die we zojuist aan CategoriesTableAdapter hebben toegevoegd, en een andere die alleen de CategoryName, Description, en BrochurePath waarden accepteert en de automatisch gegenereerde CategoriesTableAdapter instructie van de Update klasse gebruikt. De logica achter het gebruik van twee methoden is dat een gebruiker in sommige omstandigheden de afbeelding van de categorie samen met de andere velden wil bijwerken. In dat geval moet de gebruiker de nieuwe afbeelding uploaden. De binaire gegevens van de geüploade afbeelding kunnen vervolgens worden gebruikt in de UPDATE instructie. In andere gevallen is de gebruiker mogelijk alleen geïnteresseerd in het bijwerken, bijvoorbeeld de naam en beschrijving. Maar als de UPDATE instructie ook de binaire gegevens voor de Picture kolom verwacht, moeten we die informatie ook opgeven. Hiervoor moet u een extra toegang tot de database maken om de afbeeldingsgegevens voor het record op te halen dat wordt bewerkt. Daarom willen we twee UPDATE methoden. De bedrijfslogicalaag bepaalt welke moet worden gebruikt op basis van of afbeeldingsgegevens worden opgegeven bij het bijwerken van de categorie.
Om dit te vergemakkelijken, voegt u twee methoden toe aan de CategoriesBLL klasse, beide benoemd UpdateCategory. De eerste moet drie string s, een byte matrix en een int als invoerparameters accepteren; de tweede, slechts drie string s en een int. De string invoerparameters zijn voor de naam, beschrijving en het pad naar het brochurebestand van de categorie, de byte array is voor de binaire inhoud van de afbeelding van de categorie en de int identificeert de CategoryID van de record die moet worden bijgewerkt. Merk op dat de eerste overload de tweede aanroept als de doorgegeven byte matrix is null.
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateCategory(string categoryName, string description,
string brochurePath, byte[] picture, int categoryID)
{
// If no picture is specified, use other overload
if (picture == null)
return UpdateCategory(categoryName, description, brochurePath, categoryID);
// Update picture, as well
int rowsAffected = Adapter.UpdateWithPicture
(categoryName, description, brochurePath, picture, categoryID);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateCategory(string categoryName, string description,
string brochurePath, int categoryID)
{
int rowsAffected = Adapter.Update
(categoryName, description, brochurePath, categoryID);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
Stap 3: Kopiëren van de invoeg- en weergavefunctionaliteit
In de voorgaande zelfstudie hebben we een pagina gemaakt die alle categorieën in een GridView weergeeft en een DetailsView levert om nieuwe categorieën aan het systeem toe te voegen. In deze zelfstudie wordt de GridView uitgebreid met ondersteuning voor bewerken en verwijderen. In plaats van verder te werken vanuit UploadInDetailsView.aspx, laten we de wijzigingen van deze zelfstudie in plaats daarvan op de UpdatingAndDeleting.aspx-pagina in dezelfde map, ~/BinaryData, plaatsen. Kopieer en plak de declaratieve markeringen en code van UploadInDetailsView.aspx in UpdatingAndDeleting.aspx.
Begin met het openen van de UploadInDetailsView.aspx pagina. Kopieer alle declaratieve syntaxis binnen het <asp:Content> element, zoals weergegeven in afbeelding 3.
UpdatingAndDeleting.aspx Open en plak deze markering vervolgens in het <asp:Content> element. Kopieer op dezelfde manier de code van de code-behind-klasse van de UploadInDetailsView.aspx pagina naar UpdatingAndDeleting.aspx.
Afbeelding 3: Kopieer de declaratieve markering van UploadInDetailsView.aspx (klik om de afbeelding op volledige grootte weer te geven)
Nadat u de declaratieve markeringen en code hebt gekopieerd, gaat u naar UpdatingAndDeleting.aspx. U zou dezelfde uitvoer moeten zien en dezelfde gebruikerservaring hebben als met UploadInDetailsView.aspx de pagina uit de vorige zelfstudie.
Stap 4: Ondersteuning voor verwijderen toevoegen aan ObjectDataSource en GridView
Zoals we hebben besproken in de zelfstudie Een overzicht van het invoegen, bijwerken en verwijderen van gegevens , biedt GridView ingebouwde mogelijkheden voor verwijderen en kunnen deze mogelijkheden worden ingeschakeld aan het vinkje van een selectievakje als de onderliggende gegevensbron van het raster ondersteuning biedt voor verwijderen. Op dit moment biedt de ObjectDataSource waaraan gridview is gebonden (CategoriesDataSource) geen ondersteuning voor het verwijderen.
Als u dit wilt verhelpen, klikt u op de optie Gegevensbron configureren vanuit de infotag ObjectDataSource om de wizard te starten. In het eerste scherm ziet u dat de ObjectDataSource is geconfigureerd voor gebruik met de CategoriesBLL klasse. Druk op Volgende. Op dit moment worden alleen de ObjectDataSource-s InsertMethod en SelectMethod -eigenschappen opgegeven. De wizard heeft echter automatisch de vervolgkeuzelijsten op de tabbladen BIJWERKEN en VERWIJDEREN ingevuld met respectievelijk de UpdateCategory- en DeleteCategory-methoden. Dit komt doordat we in de CategoriesBLL klasse deze methoden hebben gemarkeerd met behulp van de DataObjectMethodAttribute standaardmethoden voor het bijwerken en verwijderen van deze methoden.
Stel voorlopig de vervolgkeuzelijst UPDATE-tabbladen in op (Geen), maar laat de vervolgkeuzelijst DELETE-tabbladen ingesteld op DeleteCategory. In stap 6 gaan we terug naar deze wizard om updateondersteuning toe te voegen.
Afbeelding 4: De ObjectDataSource configureren om de DeleteCategory methode te gebruiken (klik om de afbeelding op volledige grootte weer te geven)
Opmerking
Wanneer u de wizard hebt voltooid, kan Visual Studio u vragen of u velden en sleutels wilt vernieuwen, waarmee de velden voor gegevenswebbesturingselementen opnieuw worden gegenereerd. Kies Nee, want als u Ja kiest, worden alle veldaanpassingen die u hebt aangebracht, overschreven.
ObjectDataSource bevat nu een waarde voor zijn DeleteMethod-eigenschap, evenals een DeleteParameter. Onthoud dat wanneer u de wizard gebruikt om de methoden op te geven, Visual Studio de OldValuesParameterFormatString eigenschap van ObjectDataSource instelt op original_{0}, wat problemen veroorzaakt met de aanroepen van de bijwerk- en verwijdermethoden. Verwijder deze eigenschap dus helemaal of stel deze opnieuw in op de standaardwaarde. {0} Als u uw geheugen op deze eigenschap ObjectDataSource wilt vernieuwen, raadpleegt u de zelfstudie Een overzicht van het invoegen, bijwerken en verwijderen van gegevens .
Nadat de wizard is voltooid en de OldValuesParameterFormatStringfoutopmaak is opgelost, moet de declaratieve markering van ObjectDataSource er ongeveer als volgt uitzien:
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="{0}" SelectMethod="GetCategories"
TypeName="CategoriesBLL" InsertMethod="InsertWithPicture"
DeleteMethod="DeleteCategory">
<InsertParameters>
<asp:Parameter Name="categoryName" Type="String" />
<asp:Parameter Name="description" Type="String" />
<asp:Parameter Name="brochurePath" Type="String" />
<asp:Parameter Name="picture" Type="Object" />
</InsertParameters>
<DeleteParameters>
<asp:Parameter Name="categoryID" Type="Int32" />
</DeleteParameters>
</asp:ObjectDataSource>
Nadat u de ObjectDataSource hebt geconfigureerd, voegt u de mogelijkheden voor verwijderen toe aan GridView door het selectievakje Verwijderen inschakelen in te schakelen vanuit de infotag van GridView. Hiermee wordt een CommandField toegevoegd aan de GridView waarvan ShowDeleteButton de eigenschap is ingesteld op true.
Afbeelding 5: Ondersteuning voor verwijderen in de Rasterweergave inschakelen (klik om de volledige afbeelding weer te geven)
Neem even de tijd om de verwijderfunctionaliteit te testen. Er is een vreemde sleutel tussen de Products tabel s CategoryID en de Categories tabel s CategoryID, dus zal je een vreemde-sleutel-beperkingsuitzondering krijgen als je een van de eerste acht categorieën probeert te verwijderen. Als u deze functionaliteit wilt testen, voegt u een nieuwe categorie toe, met zowel een brochure als een afbeelding. Mijn testcategorie, weergegeven in afbeelding 6, bevat een testbrochurebestand met de naam Test.pdf en een testafbeelding. In afbeelding 7 ziet u de GridView nadat de testcategorie is toegevoegd.
Afbeelding 6: Een testcategorie toevoegen met een brochure en afbeelding (klik hier om de afbeelding volledig weer te geven)
Afbeelding 7: Na het invoegen van de testcategorie wordt deze weergegeven in de Rasterweergave (klik om de afbeelding op volledige grootte weer te geven)
Vernieuw de Solution Explorer in Visual Studio. U ziet nu een nieuw bestand in de ~/Brochures map ( Test.pdf zie afbeelding 8).
Klik vervolgens op de koppeling Verwijderen in de rij Categorie testen, waardoor de pagina wordt teruggeslagen en de CategoriesBLL klassemethode DeleteCategory wordt geactiveerd. Hiermee wordt de DAL-methode Delete aangeroepen, waardoor de juiste DELETE instructie naar de database wordt verzonden. De gegevens worden vervolgens opnieuw gekoppeld aan de GridView en de markup wordt teruggestuurd naar de client zonder dat de testcategorie aanwezig is.
Hoewel de verwijderwerkstroom de record Testcategorie uit de Categories tabel heeft verwijderd, is het brochurebestand niet verwijderd uit het bestandssysteem van de webserver. Vernieuw Solution Explorer en u ziet dat Test.pdf deze zich nog steeds in de ~/Brochures map bevindt.
Afbeelding 8: Het Test.pdf bestand is niet verwijderd uit het bestandssysteem van de webserver
Stap 5: Het brochurebestand van verwijderde categorie verwijderen
Een van de voordelen van het opslaan van binaire gegevens buiten de database is dat er extra stappen moeten worden ondernomen om deze bestanden op te schonen wanneer de bijbehorende databaserecord wordt verwijderd. GridView en ObjectDataSource bieden gebeurtenissen die zowel vóór als nadat het verwijdercommando is uitgevoerd plaatsvinden. We moeten eigenlijk gebeurtenis-handlers maken voor zowel de gebeurtenissen vóór als na de actie. Voordat de Categories record wordt verwijderd, moeten we het pad van het PDF-bestand bepalen, maar we willen het PDF-bestand niet verwijderen voordat de categorie wordt verwijderd voor het geval er een uitzondering is en de categorie niet wordt verwijderd.
De gebeurtenis GridView RowDeleting wordt geactiveerd voordat de opdracht ObjectDataSource s delete is aangeroepen, terwijl de RowDeleted gebeurtenis daarna wordt geactiveerd. Maak gebeurtenis-handlers voor deze twee gebeurtenissen met behulp van de volgende code:
// A page variable to "remember" the deleted category's BrochurePath value
string deletedCategorysPdfPath = null;
protected void Categories_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
// Determine the PDF path for the category being deleted...
int categoryID = Convert.ToInt32(e.Keys["CategoryID"]);
CategoriesBLL categoryAPI = new CategoriesBLL();
Northwind.CategoriesDataTable categories =
categoryAPI.GetCategoryByCategoryID(categoryID);
Northwind.CategoriesRow category = categories[0];
if (category.IsBrochurePathNull())
deletedCategorysPdfPath = null;
else
deletedCategorysPdfPath = category.BrochurePath;
}
protected void Categories_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
// Delete the brochure file if there were no problems deleting the record
if (e.Exception == null)
{
// Is there a file to delete?
if (deletedCategorysPdfPath != null)
{
System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath));
}
}
}
In de RowDeleting gebeurtenis-handler wordt de CategoryID rij die wordt verwijderd uit de GridView-verzameling DataKeys opgehaald, die toegankelijk is in deze gebeurtenis-handler via de e.Keys verzameling. Vervolgens wordt de CategoriesBLL klasse s GetCategoryByCategoryID(categoryID) aangeroepen om informatie te retourneren over de record die wordt verwijderd. Als het geretourneerde CategoriesDataRow object een niet-NULL``BrochurePath waarde heeft, wordt het opgeslagen in de paginavariabele deletedCategorysPdfPath, zodat het bestand kan worden verwijderd in de RowDeleted gebeurtenis-handler.
Opmerking
In plaats van de BrochurePath details op te halen voor de Categories record die in de RowDeleting gebeurtenishandler wordt verwijderd, konden we de BrochurePath ook aan de eigenschap van GridView DataKeyNames toevoegen en de recordwaarde door de e.Keys verzameling benaderen. Als u dit doet, wordt de grootte van de weergavestaat van de GridView enigszins vergroot, maar vermindert het de hoeveelheid benodigde code en voorkomt het een extra toegang tot de database.
Nadat de onderliggende verwijderopdracht van ObjectDataSource is aangeroepen, wordt de gebeurtenis-handler van RowDeleted GridView geactiveerd. Als er geen uitzonderingen waren bij het verwijderen van de gegevens en er een waarde voor deletedCategorysPdfPathis, wordt het PDF-bestand uit het bestandssysteem verwijderd. Houd er rekening mee dat deze extra code niet nodig is om de binaire gegevens van de categorie op te schonen die aan de afbeelding zijn gekoppeld. Dat komt doordat de afbeeldingsgegevens rechtstreeks in de database worden opgeslagen, dus als u de Categories rij verwijdert, worden ook de afbeeldingsgegevens van die categorie verwijderd.
Nadat u de twee gebeurtenis-handlers hebt toegevoegd, voert u deze testcase opnieuw uit. Wanneer u de categorie verwijdert, wordt het bijbehorende PDF-bestand ook verwijderd.
Het bijwerken van een bestaande record gekoppelde binaire gegevens biedt enkele interessante uitdagingen. De rest van deze zelfstudie gaat verder met het toevoegen van updatemogelijkheden aan de brochure en afbeelding. Stap 6 verkent technieken voor het bijwerken van de brochuregegevens terwijl stap 7 kijkt naar het bijwerken van de afbeelding.
Stap 6: De brochure van een categorie bijwerken
Zoals besproken in de zelfstudie Een overzicht van het invoegen, bijwerken en verwijderen van gegevens , biedt de GridView ingebouwde ondersteuning voor bewerken op rijniveau die kan worden geïmplementeerd door het aanvinken van een selectievakje als de onderliggende gegevensbron op de juiste manier is geconfigureerd.
CategoriesDataSource De ObjectDataSource is momenteel nog niet geconfigureerd voor het toevoegen van ondersteuning voor het bijwerken, dus laten we die toevoegen.
Klik op de koppeling Gegevensbron configureren vanuit de wizard ObjectDataSource en ga verder met de tweede stap. Vanwege de DataObjectMethodAttribute die in CategoriesBLL wordt gebruikt, moet de vervolgkeuzelijst UPDATE automatisch worden gevuld met de UpdateCategory overload die vier invoerparameters accepteert (voor alle kolommen behalve Picture). Wijzig dit zodat de overbelasting met vijf parameters wordt gebruikt.
Afbeelding 9: De ObjectDataSource configureren voor het gebruik van de UpdateCategory methode waarvoor een parameter Picture is opgenomen (klik om de volledige afbeelding weer te geven)
ObjectDataSource zal nu een waarde bevatten voor zijn UpdateMethod-eigenschap en de bijbehorende UpdateParameter-waarden. Zoals vermeld in stap 4, stelt Visual Studio de ObjectDataSource OldValuesParameterFormatString eigenschap in op original_{0} wanneer u de wizard Gegevensbron configureren gebruikt. Dit veroorzaakt problemen met de aanroepen van de update- en verwijdermethode. Verwijder deze eigenschap dus helemaal of stel deze opnieuw in op de standaardwaarde. {0}
Nadat de wizard is voltooid en de OldValuesParameterFormatStringwizard is opgelost, moet de declaratieve markering van ObjectDataSource er als volgt uitzien:
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="{0}" SelectMethod="GetCategories"
TypeName="CategoriesBLL" InsertMethod="InsertWithPicture"
DeleteMethod="DeleteCategory" UpdateMethod="UpdateCategory">
<InsertParameters>
<asp:Parameter Name="categoryName" Type="String" />
<asp:Parameter Name="description" Type="String" />
<asp:Parameter Name="brochurePath" Type="String" />
<asp:Parameter Name="picture" Type="Object" />
</InsertParameters>
<DeleteParameters>
<asp:Parameter Name="categoryID" Type="Int32" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="categoryName" Type="String" />
<asp:Parameter Name="description" Type="String" />
<asp:Parameter Name="brochurePath" Type="String" />
<asp:Parameter Name="picture" Type="Object" />
<asp:Parameter Name="categoryID" Type="Int32" />
</UpdateParameters>
</asp:ObjectDataSource>
Als u de ingebouwde bewerkingsfuncties van GridView wilt inschakelen, schakelt u de optie Bewerken inschakelen in vanuit de smarttag van GridView. Hiermee stelt u de eigenschap van CommandField ShowEditButton in op true, wat resulteert in het toevoegen van een knop Bewerk (en de knoppen Bijwerken en Annuleren voor de rij die wordt bewerkt).
Afbeelding 10: De GridView configureren ter ondersteuning van bewerken (klik om de volledige afbeelding weer te geven)
Ga naar de pagina via een browser en klik op een van de knoppen Bewerken van de rij. De CategoryName en Description BoundFields worden weergegeven als tekstvakken. Het BrochurePath TemplateField mist een EditItemTemplate, dus blijft het een ItemTemplate koppeling naar de brochure weergeven. Het Picture ImageField wordt weergegeven als een tekstvak waarbij de eigenschap Text wordt toegewezen de waarde van het DataImageUrlField ImageField, in dit geval CategoryID.
Afbeelding 11: De GridView mist een bewerkingsinterface voor BrochurePath (klik om de afbeelding op volledige grootte weer te geven)
De bewerkingsinterface aanpassenBrochurePath
We moeten een bewerkingsinterface maken voor het BrochurePath TemplateField, een interface waarmee de gebruiker het volgende kan doen:
- Laat de brochure van de categorie as-is,
- Werk de brochure van de categorie bij door een nieuwe brochure te uploaden of
- Verwijder de brochure van de categorie helemaal (in het geval dat de categorie geen bijbehorende brochure meer heeft).
We moeten ook de Picture bewerkingsinterface van ImageField bijwerken, maar we komen hier in stap 7 bij.
Klik in de infolabel van GridView op de koppeling Sjablonen bewerken en selecteer de BrochurePath TemplateFields EditItemTemplate in de vervolgkeuzelijst. Voeg een RadioButtonList-webcontrole toe aan deze sjabloon en stel de ID-eigenschap in op BrochureOptions en de AutoPostBack-eigenschap in op true. Klik in het venster Eigenschappen op de drie puntjes in de Items-eigenschap om de ListItem-verzamelingseditor te openen. Voeg de volgende drie opties toe met Value respectievelijk s 1, 2 en 3:
- Huidige brochure gebruiken
- Huidige brochure verwijderen
- Nieuwe brochure uploaden
Stel de eerste ListItem eigenschap Selected naar true.
Afbeelding 12: Voeg drie ListItem s toe aan de RadioButtonList
Voeg onder de RadioButtonList een FileUpload-besturingselement toe met de naam BrochureUpload. Stel de eigenschap Visible ervan in op false.
Afbeelding 13: Voeg een RadioButtonList- en FileUpload-besturingselement toe aan de EditItemTemplate knop (Klik om de volledige afbeelding weer te geven)
Deze RadioButtonList biedt de drie opties voor de gebruiker. Het idee is dat het besturingselement FileUpload alleen wordt weergegeven als de laatste optie, Nieuwe brochure uploaden, is geselecteerd. Hiervoor maakt u een gebeurtenis-handler voor de gebeurtenis RadioButtonList SelectedIndexChanged en voegt u de volgende code toe:
protected void BrochureOptions_SelectedIndexChanged(object sender, EventArgs e)
{
// Get a reference to the RadioButtonList and its Parent
RadioButtonList BrochureOptions = (RadioButtonList)sender;
Control parent = BrochureOptions.Parent;
// Now use FindControl("controlID") to get a reference of the
// FileUpload control
FileUpload BrochureUpload =
(FileUpload)parent.FindControl("BrochureUpload");
// Only show BrochureUpload if SelectedValue = "3"
BrochureUpload.Visible = (BrochureOptions.SelectedValue == "3");
}
Omdat de besturingselementen RadioButtonList en FileUpload zich in een sjabloon bevinden, moeten we een beetje code schrijven om programmatisch toegang te krijgen tot deze besturingselementen. De SelectedIndexChanged event-handler krijgt een verwijzing naar de RadioButtonList in de sender invoerparameter. Om het controle-element FileUpload op te halen, moeten we het bovenliggende controle-element RadioButtonList ophalen en de FindControl("controlID") methode daar gebruiken. Zodra we een verwijzing hebben naar zowel de besturingselementen RadioButtonList als FileUpload, wordt de eigenschap van de besturingselement FileUpload Visible alleen ingesteld op true als de eigenschap van de RadioButtonList SelectedValue gelijk is aan 3, wat de Value voor het Uploaden van de nieuwe brochure ListItem is.
Met deze code op zijn plaats, neem even de tijd om de bewerkingsinterface te testen. Klik voor een rij op de knop Bewerken. In eerste instantie moet de optie Huidige brochure gebruiken worden geselecteerd. Als u de geselecteerde index wijzigt, wordt een postback veroorzaakt. Als de derde optie is geselecteerd, wordt het besturingselement FileUpload weergegeven, anders wordt het verborgen. Afbeelding 14 toont de bewerkingsinterface wanneer de knop Bewerken voor het eerst wordt geklikt; Afbeelding 15 toont de interface nadat de optie Nieuwe brochure uploaden is geselecteerd.
Afbeelding 14: In eerste instantie is de optie Huidige brochure gebruiken geselecteerd (klik om de afbeelding volledig weer te geven)
Afbeelding 15: Als u de optie Nieuwe brochure uploaden kiest, wordt het besturingselement FileUpload weergegeven (klik hier om de volledige afbeelding weer te geven)
Het brochurebestand opslaan en deBrochurePathkolom bijwerken
Wanneer op de knop Bijwerken van GridView wordt geklikt, wordt de RowUpdating gebeurtenis geactiveerd. De update-opdracht van de ObjectDataSource wordt aangeroepen en vervolgens wordt de RowUpdated-gebeurtenis van de GridView geactiveerd. Net als bij het verwijderen van de workflow moeten we eventhandlers maken voor beide gebeurtenissen. In de RowUpdating eventafhandelaar moeten we bepalen welke actie moet worden ondernomen op basis van SelectedValue van de BrochureOptions RadioButtonList.
- Als de waarde
SelectedValue1 is, willen we dezelfdeBrochurePathinstelling blijven gebruiken. Daarom moeten we de parameter ObjectDataSourcebrochurePathinstellen op de bestaandeBrochurePathwaarde van de record die wordt bijgewerkt. De parameter ObjectDataSource sbrochurePathkan worden ingesteld met behulp vane.NewValues["brochurePath"] = value. - Als
SelectedValue2 is, willen we de waarde vanBrochurePathin het record instellen opNULL. Dit kan worden bereikt door de parameter van ObjectDataSource in te stellen opbrochurePath, waardoor een databaseNothingwordt gebruikt in deNULLinstructie. Als er een bestaand brochurebestand is dat wordt verwijderd, moeten we het bestaande bestand verwijderen. We willen dit echter alleen doen als de update is voltooid zonder een uitzondering op te geven. - Als de waarde
SelectedValue3 is, willen we ervoor zorgen dat de gebruiker een PDF-bestand heeft geüpload en het vervolgens opslaat in het bestandssysteem en de kolomwaarde van de record bijwerktBrochurePath. Als er bovendien een bestaand brochurebestand is dat wordt vervangen, moeten we het vorige bestand verwijderen. We willen dit echter alleen doen als de update is voltooid zonder een uitzondering op te geven.
De stappen die moeten worden voltooid wanneer radiobuttonlist s SelectedValue 3 is, zijn vrijwel identiek aan de stappen die worden gebruikt door de gebeurtenis-handler van ItemInserting DetailsView. Deze gebeurtenis-handler wordt uitgevoerd wanneer een nieuwe categorierecord wordt toegevoegd vanuit het Besturingselement DetailsView dat we in de vorige zelfstudie hebben toegevoegd. Daarom moeten we deze functionaliteit herstructureren in afzonderlijke methoden. Ik heb met name de algemene functionaliteit naar twee methoden verplaatst:
-
ProcessBrochureUpload(FileUpload, out bool)accepteert als invoer een FileUpload-besturingselementinstantie en een booleaanse uitvoerwaarde die aangeeft of de verwijder- of bewerkingsactie moet worden voortgezet of moet worden geannuleerd vanwege een validatiefout. Deze methode retourneert het pad naar het opgeslagen bestand ofnullals er geen bestand is opgeslagen. -
DeleteRememberedBrochurePathverwijdert het bestand dat wordt aangeduid door het pad in de paginavariabeledeletedCategorysPdfPathalsdeletedCategorysPdfPathnietnullis.
De code voor deze twee methoden volgt. Let op de overeenkomst tussen ProcessBrochureUpload en de event-handler van de DetailsView uit de vorige zelfstudie. In deze tutorial heb ik de gebeurtenishandlers van DetailsView bijgewerkt om deze nieuwe methoden te gebruiken. Download de code die bij deze tutorial hoort om de wijzigingen in de event handlers van de DetailsView te bekijken.
private string ProcessBrochureUpload
(FileUpload BrochureUpload, out bool CancelOperation)
{
CancelOperation = false; // by default, do not cancel operation
if (BrochureUpload.HasFile)
{
// Make sure that a PDF has been uploaded
if (string.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName),
".pdf", true) != 0)
{
UploadWarning.Text =
"Only PDF documents may be used for a category's brochure.";
UploadWarning.Visible = true;
CancelOperation = true;
return null;
}
const string BrochureDirectory = "~/Brochures/";
string brochurePath = BrochureDirectory + BrochureUpload.FileName;
string fileNameWithoutExtension =
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName);
int iteration = 1;
while (System.IO.File.Exists(Server.MapPath(brochurePath)))
{
brochurePath = string.Concat(BrochureDirectory, fileNameWithoutExtension,
"-", iteration, ".pdf");
iteration++;
}
// Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath));
return brochurePath;
}
else
{
// No file uploaded
return null;
}
}
private void DeleteRememberedBrochurePath()
{
// Is there a file to delete?
if (deletedCategorysPdfPath != null)
{
System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath));
}
}
De GridView-s RowUpdating - en RowUpdated gebeurtenis-handlers gebruiken de ProcessBrochureUpload en DeleteRememberedBrochurePath methoden, zoals in de volgende code wordt weergegeven:
protected void Categories_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
// Reference the RadioButtonList
RadioButtonList BrochureOptions =
(RadioButtonList)Categories.Rows[e.RowIndex].FindControl("BrochureOptions");
// Get BrochurePath information about the record being updated
int categoryID = Convert.ToInt32(e.Keys["CategoryID"]);
CategoriesBLL categoryAPI = new CategoriesBLL();
Northwind.CategoriesDataTable categories =
categoryAPI.GetCategoryByCategoryID(categoryID);
Northwind.CategoriesRow category = categories[0];
if (BrochureOptions.SelectedValue == "1")
{
// Use current value for BrochurePath
if (category.IsBrochurePathNull())
e.NewValues["brochurePath"] = null;
else
e.NewValues["brochurePath"] = category.BrochurePath;
}
else if (BrochureOptions.SelectedValue == "2")
{
// Remove the current brochure (set it to NULL in the database)
e.NewValues["brochurePath"] = null;
}
else if (BrochureOptions.SelectedValue == "3")
{
// Reference the BrochurePath FileUpload control
FileUpload BrochureUpload =
(FileUpload)Categories.Rows[e.RowIndex].FindControl("BrochureUpload");
// Process the BrochureUpload
bool cancelOperation = false;
e.NewValues["brochurePath"] =
ProcessBrochureUpload(BrochureUpload, out cancelOperation);
e.Cancel = cancelOperation;
}
else
{
// Unknown value!
throw new ApplicationException(
string.Format("Invalid BrochureOptions value, {0}",
BrochureOptions.SelectedValue));
}
if (BrochureOptions.SelectedValue == "2" ||
BrochureOptions.SelectedValue == "3")
{
// "Remember" that we need to delete the old PDF file
if (category.IsBrochurePathNull())
deletedCategorysPdfPath = null;
else
deletedCategorysPdfPath = category.BrochurePath;
}
}
protected void Categories_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
// If there were no problems and we updated the PDF file,
// then delete the existing one
if (e.Exception == null)
{
DeleteRememberedBrochurePath();
}
}
RowUpdating Merk op hoe de gebeurtenis-handler een reeks voorwaardelijke instructies gebruikt om de juiste actie uit te voeren op basis van de eigenschapswaarde van de BrochureOptions RadioButtonListSelectedValue.
Met deze code kunt u een categorie bewerken en de huidige brochure gebruiken, geen brochure gebruiken of een nieuwe categorie uploaden. Probeer het maar eens. Stel onderbrekingspunten in de RowUpdating en RowUpdated gebeurtenis-handlers in om een idee te krijgen van de werkstroom.
Stap 7: Een nieuwe afbeelding uploaden
De Picture bewerkingsinterface van ImageField wordt weergegeven als een tekstvak dat is gevuld met de waarde van de DataImageUrlField eigenschap. Tijdens de bewerkingswerkstroom geeft GridView een parameter door aan de ObjectDataSource met de parameternaam de waarde van de eigenschap ImageField en DataImageUrlField de parameter-waarde die in het tekstvak in de bewerkingsinterface is ingevoerd. Dit gedrag is geschikt wanneer de afbeelding wordt opgeslagen als een bestand op het bestandssysteem en de DataImageUrlField volledige URL van de afbeelding bevat. In dergelijke omstandigheden geeft de bewerkingsinterface de URL van de afbeelding weer in het tekstvak, die de gebruiker kan wijzigen en weer in de database kan opslaan. Deze standaardinterface staat de gebruiker niet toe een nieuwe afbeelding te uploaden, maar hiermee kan de URL van de afbeelding worden gewijzigd van de huidige waarde in een andere afbeelding. Voor deze zelfstudie volstaat echter de standaardbewerkingsinterface van ImageField niet, omdat de Picture binaire gegevens rechtstreeks in de database worden opgeslagen en de DataImageUrlField eigenschap alleen de CategoryID bevat.
Als u beter wilt weten wat er gebeurt in onze zelfstudie wanneer een gebruiker een rij bewerkt met een ImageField, kunt u het volgende voorbeeld bekijken: een gebruiker bewerkt een rij met CategoryID 10, waardoor het Picture ImageField wordt weergegeven als een tekstvak met de waarde 10. Stel dat de gebruiker de waarde in dit tekstvak wijzigt in 50 en op de knop Bijwerken klikt. Een postback vindt plaats en de GridView maakt aanvankelijk een parameter genaamd CategoryID met de waarde 50. Echter, voordat de GridView deze parameter (en de parameters CategoryName en Description) verzendt, worden de waarden uit de DataKeys collectie toegevoegd. Daarom wordt de CategoryID parameter overschreven met de onderliggende CategoryID waarde van de huidige rij, 10. Kortom, de bewerkingsinterface van ImageField heeft geen invloed op de bewerkingswerkstroom van deze tutorial, omdat de namen van de eigenschap ImageField DataImageUrlField en de waarde van het raster DataKey identiek zijn.
Hoewel ImageField het eenvoudig maakt om een afbeelding weer te geven op basis van databasegegevens, willen we geen tekstvak in de bewerkingsinterface opgeven. In plaats daarvan willen we een FileUpload-besturingselement aanbieden dat de eindgebruiker kan gebruiken om de afbeelding van de categorie te wijzigen. In tegenstelling tot de BrochurePath waarde hebben we voor deze zelfstudies besloten om te vereisen dat elke categorie een afbeelding moet hebben. Daarom hoeven we de gebruiker niet de optie te geven om aan te geven dat er geen gekoppelde afbeelding is; de gebruiker kan ofwel een nieuwe afbeelding uploaden of de huidige afbeelding laten staan as-is.
Als u de bewerkingsinterface van ImageField wilt aanpassen, moeten we deze converteren naar een TemplateField. Klik in de smarttag van GridView op de koppeling 'Kolommen bewerken', selecteer het ImageField en klik op de koppeling 'Converteer dit veld naar een TemplateField'.
Afbeelding 16: Het ImageField converteren naar een TemplateField
Als u ImageField op deze manier converteert naar een TemplateField, wordt een TemplateField met twee sjablonen gegenereerd. Zoals de volgende declaratieve syntaxis laat zien, bevat het ItemTemplate een afbeeldingswebbesturingselement waarvan ImageUrl de eigenschap wordt toegewezen met behulp van de syntaxis van databinding op basis van de ImageField-s DataImageUrlField en DataImageUrlFormatString eigenschappen. Het EditItemTemplate bevat een tekstvak waarvan de Text eigenschap is gebonden aan de waarde die is gespecificeerd door de DataImageUrlField eigenschap.
<asp:TemplateField>
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Eval("CategoryID") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Image ID="Image1" runat="server"
ImageUrl='<%# Eval("CategoryID",
"DisplayCategoryPicture.aspx?CategoryID={0}") %>' />
</ItemTemplate>
</asp:TemplateField>
We moeten het EditItemTemplate bijwerken om een FileUpload-besturingselement te gebruiken. Klik in de infolabel van GridView op de koppeling Sjablonen bewerken en selecteer vervolgens de Picture TemplateFields EditItemTemplate in de vervolgkeuzelijst. In de sjabloon ziet u dat een tekstvak dit verwijdert. Sleep vervolgens een FileUpload-besturingselement van de werkset naar de sjabloon, waarbij u het besturingselement instelt ID op PictureUpload. Voeg ook de tekst toe om de afbeelding van de categorie te wijzigen door een nieuwe afbeelding op te geven. Als u de afbeelding van de categorie hetzelfde wilt houden, laat u het veld ook leeg voor de sjabloon.
Afbeelding 17: Voeg een FileUpload-besturingselement toe aan de EditItemTemplate afbeelding (klik om de afbeelding volledig weer te geven)
Nadat u de bewerkingsinterface hebt aangepast, bekijkt u de voortgang in een browser. Wanneer u een rij bekijkt in de alleen-lezenmodus, wordt de afbeelding van de categorie weergegeven zoals voorheen, maar als u op de knop Bewerken klikt, wordt de afbeeldingskolom als tekst weergegeven met een FileUpload-besturingselement.
Afbeelding 18: De bewerkingsinterface bevat een FileUpload-besturingselement (klik om de afbeelding op volledige grootte weer te geven)
De ObjectDataSource is geconfigureerd om de methode CategoriesBLL van de klasse UpdateCategory aan te roepen, die de binaire gegevens van de afbeelding accepteert als een matrix byte als invoer. Als deze matrix echter een null waarde heeft, wordt de alternatieve UpdateCategory overbelasting aangeroepen, die de UPDATE SQL-instructie uitgeeft die de Picture kolom niet wijzigt, waardoor de huidige afbeelding van de categorie intact blijft. Daarom moeten we in de gebeurtenis-handler van RowUpdating GridView programmatisch verwijzen naar het PictureUpload besturingselement FileUpload en bepalen of een bestand is geüpload. Als er geen bestand is geüpload, willen we geen waarde opgeven voor de picture parameter. Aan de andere kant, als een bestand is geüpload in het PictureUpload FileUpload-besturingselement, willen we ervoor zorgen dat het een JPG-bestand is. Als dat het is, kunnen we de binaire inhoud naar de ObjectDataSource verzenden via de picture parameter.
Net als bij de code die in stap 6 wordt gebruikt, bestaat veel van de code die hier nodig is al in de gebeurtenis-handler van ItemInserting DetailsView. Daarom heb ik de algemene functionaliteit geherstructureerd in een nieuwe methode ValidPictureUploaden de ItemInserting gebeurtenis-handler bijgewerkt om deze methode te gebruiken.
Voeg de volgende code toe aan het begin van de gebeurtenis-handler van GridView RowUpdating . Het is belangrijk dat deze code vóór de code komt die het brochurebestand opslaat, omdat we de brochure niet willen opslaan in het bestandssysteem van de webserver als er een ongeldig afbeeldingsbestand wordt geüpload.
// Reference the PictureUpload FileUpload
FileUpload PictureUpload =
(FileUpload)Categories.Rows[e.RowIndex].FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
// Make sure the picture upload is valid
if (ValidPictureUpload(PictureUpload))
{
e.NewValues["picture"] = PictureUpload.FileBytes;
}
else
{
// Invalid file upload, cancel update and exit event handler
e.Cancel = true;
return;
}
}
De ValidPictureUpload(FileUpload) methode neemt een FileUpload-besturingselement op als enige invoerparameter en controleert de geüploade bestandsextensie om ervoor te zorgen dat het geüploade bestand een JPG is. Het wordt alleen aangeroepen als een afbeeldingsbestand wordt geüpload. Als er geen bestand wordt geüpload, is de afbeeldingsparameter niet ingesteld en wordt daarom de standaardwaarde van null gebruikt. Als een afbeelding is geüpload en ValidPictureUpload geretourneerd true, wordt aan de picture parameter de binaire gegevens van de geüploade afbeelding toegewezen. Als de methode retourneert false, wordt de updatewerkstroom geannuleerd en wordt de gebeurtenis-handler afgesloten.
De ValidPictureUpload(FileUpload) methodecode, die is geherstructureerd vanuit de gebeurtenis-handler van ItemInserting DetailsView, volgt:
private bool ValidPictureUpload(FileUpload PictureUpload)
{
// Make sure that a JPG has been uploaded
if (string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpg", true) != 0 &&
string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpeg", true) != 0)
{
UploadWarning.Text =
"Only JPG documents may be used for a category's picture.";
UploadWarning.Visible = true;
return false;
}
else
{
return true;
}
}
Stap 8: De oorspronkelijke categorieënafbeeldingen vervangen door JPG's
Zoals u weet, zijn de oorspronkelijke acht categorieën afbeeldingen bitmapbestanden die zijn verpakt in een OLE-header. Nu we de mogelijkheid hebben toegevoegd om een bestaande recordfoto te bewerken, kunt u deze bitmaps vervangen door JPG's. Als u de huidige categorieafbeeldingen wilt blijven gebruiken, kunt u deze converteren naar JPG's door de volgende stappen uit te voeren:
- Sla de bitmapafbeeldingen op de harde schijf op. Ga naar de
UpdatingAndDeleting.aspxpagina in uw browser en klik voor elk van de eerste acht categorieën met de rechtermuisknop op de afbeelding en kies ervoor om de afbeelding op te slaan. - Open de afbeelding in de gewenste afbeeldingseditor. U kunt bijvoorbeeld Microsoft Paint gebruiken.
- Sla de bitmap op als JPG-afbeelding.
- Werk de afbeelding van de categorie bij via de bewerkingsinterface met behulp van het JPG-bestand.
Na het bewerken van een categorie en het uploaden van de JPG-afbeelding, wordt de afbeelding niet weergegeven in de browser omdat de DisplayCategoryPicture.aspx pagina de eerste 78 bytes verwijdert van de afbeeldingen van de eerste acht categorieën. Los dit op door de code te verwijderen waarmee de OLE-header wordt verwijderd. Nadat u dit hebt gedaan, moet de DisplayCategoryPicture.aspx``Page_Load gebeurtenis-handler alleen de volgende code hebben:
protected void Page_Load(object sender, EventArgs e)
{
int categoryID = Convert.ToInt32(Request.QueryString["CategoryID"]);
// Get information about the specified category
CategoriesBLL categoryAPI = new CategoriesBLL();
Northwind.CategoriesDataTable categories = _
categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID);
Northwind.CategoriesRow category = categories[0];
// For new categories, images are JPGs...
// Output HTTP headers providing information about the binary data
Response.ContentType = "image/jpeg";
// Output the binary data
Response.BinaryWrite(category.Picture);
}
Opmerking
De UpdatingAndDeleting.aspx pagina heeft wat meer werk nodig voor het invoegen en bewerken van interfaces. De CategoryName en Description BoundFields in de DetailsView en GridView moeten worden geconverteerd naar TemplateFields. Omdat CategoryName geen NULL-waardes toestaat, moet er een RequiredFieldValidator worden toegevoegd. En het Description tekstvak moet waarschijnlijk worden geconverteerd naar een tekstvak met meerdere regels. Ik laat deze afwerkingen als oefening voor je achter.
Samenvatting
Deze zelfstudie voltooit onze verkenning van het werken met binaire gegevens. In deze zelfstudie en de vorige drie hebben we gezien hoe binaire gegevens kunnen worden opgeslagen in het bestandssysteem of rechtstreeks in de database. Een gebruiker levert binaire gegevens aan het systeem door een bestand te selecteren op de harde schijf en het te uploaden naar de webserver, waar het kan worden opgeslagen op het bestandssysteem of in de database kan worden ingevoegd. ASP.NET 2.0 bevat een FileUpload-besturingselement waarmee het bieden van zo'n interface net zo eenvoudig is als slepen en neerzetten. Zoals aangegeven in de zelfstudie Bestanden uploaden , is het besturingselement FileUpload echter alleen geschikt voor relatief kleine bestandsuploads, in het ideale voorbeeld niet groter dan een megabyte. We hebben ook verkend hoe u geüploade gegevens koppelt aan het onderliggende gegevensmodel en hoe u de binaire gegevens uit bestaande records bewerkt en verwijdert.
In onze volgende reeks handleidingen worden verschillende cachingtechnieken verkend. Caching biedt een middel om de algehele prestaties van een toepassing te verbeteren door de resultaten van dure bewerkingen te nemen en op te slaan op een locatie die sneller toegankelijk is.
Veel plezier met programmeren!
Over de auteur
Scott Mitchell, auteur van zeven ASP/ASP.NET-boeken en oprichter van 4GuysFromRolla.com, werkt sinds 1998 met Microsoft-webtechnologieën. Scott werkt als onafhankelijk consultant, trainer en schrijver. Zijn laatste boek is Sams Teach Yourself ASP.NET 2.0 in 24 uur. Hij kan worden bereikt op mitchell@4GuysFromRolla.com.
Speciale dank aan
Deze tutorialreeks is beoordeeld door veel behulpzame beoordelers. Eindrecensent voor deze handleiding was Teresa Murphy. Bent u geïnteresseerd in het bekijken van mijn aanstaande MSDN-artikelen? Zo ja, laat iets van je horen via mitchell@4GuysFromRolla.com.