Dela via


Inkludera ett alternativ för filuppladdning när du lägger till en ny post (C#)

av Scott Mitchell

Ladda ned PDF

Den här självstudien visar hur du skapar ett webbgränssnitt som gör att användaren både kan ange textdata och ladda upp binära filer. För att illustrera de alternativ som är tillgängliga för att lagra binära data sparas en fil i databasen medan den andra lagras i filsystemet.

Inledning

I de föregående två självstudierna utforskade vi tekniker för att lagra binära data som är associerade med programmets datamodell, tittade på hur du använder FileUpload-kontrollen för att skicka filer från klienten till webbservern och såg hur du presenterar dessa binära data i en datawebbkontroll. Vi har dock ännu inte pratat om hur du associerar uppladdade data med datamodellen.

I den här självstudien skapar vi en webbsida för att lägga till en ny kategori. Förutom Textrutor för kategorins namn och beskrivning måste den här sidan innehålla två FileUpload-kontroller en för den nya kategorins bild och en för broschyren. Den uppladdade bilden lagras direkt i den nya postens Picture kolumn, medan broschyren sparas i ~/Brochures mappen med sökvägen till filen som sparats i den nya postens BrochurePath kolumn.

Innan vi skapar den här nya webbsidan måste vi uppdatera arkitekturen. Huvudfrågan CategoriesTableAdapter hämtar inte Picture kolumnen. Därför har den automatiskt genererade Insert metoden endast indata för fälten CategoryName, Descriptionoch BrochurePath . Därför måste vi skapa en ytterligare metod i TableAdapter som frågar efter alla fyra Categories fälten. Klassen CategoriesBLL i Business Logic Layer måste också uppdateras.

Steg 1: Lägga till enInsertWithPicturemetod iCategoriesTableAdapter

När vi skapade CategoriesTableAdapter i självstudien Skapa ett dataåtkomstlager, konfigurerade vi den för att automatiskt generera INSERT, UPDATE, och DELETE-instruktioner baserat på huvudfråga. Dessutom instruerade vi TableAdapter att använda DB Direct-metoden, som skapade metoderna Insert, Updateoch Delete. Dessa metoder kör de automatiskt genererade INSERT- , UPDATEoch DELETE -uttrycken och accepterar därför indataparametrar baserat på de kolumner som returneras av huvudfrågan. I tutorialen Att ladda upp filer utökade CategoriesTableAdapter vi huvudfrågan för att använda BrochurePath kolumnen.

CategoriesTableAdapter Eftersom huvudfrågan inte refererar till Picture kolumnen kan vi varken lägga till en ny post eller uppdatera en befintlig post med ett värde för Picture kolumnen. För att samla in den här informationen kan vi antingen skapa en ny metod i TableAdapter som används specifikt för att infoga en post med binära data, eller så kan vi anpassa den automatiskt genererade INSERT instruktionen. Problemet med att anpassa den automatiskt genererade INSERT instruktionen är att vi riskerar att våra anpassningar skrivs över av guiden. Anta till exempel att vi har anpassat INSERT-uttalandet så att det inkluderar användningen av Picture-kolumnen. Detta skulle uppdatera metoden TableAdapter s Insert så att den innehåller ytterligare en indataparameter för kategoribildens binära data. Vi kan sedan skapa en metod i affärslogiklagret för att använda den här DAL-metoden och anropa den här BLL-metoden via presentationsskiktet, och allt skulle fungera underbart. Tills nästa gång vi konfigurerade TableAdapter via konfigurationsguiden TableAdapter. När guiden har slutförts skrivs våra anpassningar till -instruktionen INSERT över, Insert metoden återgår till det gamla formuläret och koden kompileras inte längre!

Anmärkning

Den här irritationen är ett icke-problem när du använder lagrade procedurer i stället för ad hoc SQL-instruktioner. En framtida självstudiekurs utforskar hur du använder lagrade procedurer i stället för ad hoc-SQL-instruktioner i dataåtkomstlagret.

För att undvika den här potentiella huvudvärken kan vi i stället för att anpassa de automatiskt genererade SQL-uttrycken skapa en ny metod för TableAdapter. Den här metoden, med namnet InsertWithPicture, accepterar värden för kolumnerna CategoryName, Description, BrochurePathoch och Picture kör en INSERT instruktion som lagrar alla fyra värdena i en ny post.

Öppna Den typerade datauppsättningen och högerklicka på CategoriesTableAdapter sidhuvudet i Designer och välj Lägg till fråga på snabbmenyn. Detta startar tableAdapter-frågekonfigurationsguiden, som börjar med att fråga oss hur TableAdapter-frågan ska komma åt databasen. Välj Använd SQL-instruktioner och klicka på Nästa. Nästa steg frågar efter vilken typ av fråga som ska genereras. Eftersom vi skapar en fråga för att lägga till en ny post i Categories tabellen väljer du INSERT och klickar på Nästa.

Välj alternativet INFOGA

Bild 1: Välj ALTERNATIVET INFOGA (Klicka om du vill visa en bild i full storlek)

Nu måste vi specificera SQL-instruktionen INSERT . Guiden föreslår automatiskt en INSERT instruktion som motsvarar TableAdapter-huvudfrågan. I det här fallet är det en INSERT -instruktion som infogar CategoryNamevärdena , Descriptionoch BrochurePath . Uppdatera instruktionen så att kolumnen Picture ingår tillsammans med en @Picture parameter, på följande sätt:

INSERT INTO [Categories] 
    ([CategoryName], [Description], [BrochurePath], [Picture]) 
VALUES 
    (@CategoryName, @Description, @BrochurePath, @Picture)

Den sista skärmen i guiden ber oss att namnge den nya TableAdapter-metoden. Ange InsertWithPicture och klicka på Slutför.

Namnge metoden InsertWithPicture för New TableAdapter

Bild 2: Namnge metoden New TableAdapter InsertWithPicture (Klicka om du vill visa en bild i full storlek)

Steg 2: Uppdatera affärslogiklagret

Eftersom presentationslagret bara ska ha ett gränssnitt med affärslogiklagret i stället för att kringgå det för att gå direkt till dataåtkomstskiktet måste vi skapa en BLL-metod som anropar DAL-metoden som vi just skapade (InsertWithPicture). I den här självstudien skapar du en metod i CategoriesBLL klassen med namnet InsertWithPicture som accepterar som indata tre string s och en byte matris. Indataparametrarna string är för kategorins namn, beskrivning och broschyrfilsökväg, medan matrisen byte är avsedd för det binära innehållet i kategorins bild. Som följande kod visar anropar den här BLL-metoden motsvarande DAL-metod:

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Insert, false)] 
public void InsertWithPicture(string categoryName, string description, 
    string brochurePath, byte[] picture)
{
    Adapter.InsertWithPicture(categoryName, description, brochurePath, picture);
}

Anmärkning

Kontrollera att du har sparat den inskrivna datamängden innan du lägger till metoden i InsertWithPicture BLL-filen. CategoriesTableAdapter Eftersom klasskoden genereras automatiskt baserat på Typed DataSet, kommer Adapter-egenskapen inte att känna till InsertWithPicture-metoden om du inte först sparar dina ändringar i Typed DataSet.

Steg 3: Lista befintliga kategorier och deras binära data

I den här självstudien skapar vi en sida som gör att en slutanvändare kan lägga till en ny kategori i systemet, vilket ger en bild och broschyr för den nya kategorin. I föregående självstudie använde vi en GridView med templatefield och ImageField för att visa varje kategoris namn, beskrivning, bild och en länk för att ladda ned broschyren. Vi replikerar den funktionen för den här självstudien och skapar en sida som både visar alla befintliga kategorier och möjliggör skapandet av nya.

Börja med att öppna sidan DisplayOrDownload.aspx från mappen BinaryData. Gå till källvyn och kopiera GridView och ObjectDataSources deklarativa syntax och klistra in den i elementet <asp:Content> i UploadInDetailsView.aspx. Glöm inte heller att kopiera över GenerateBrochureLink-metoden från code-behind-klassen DisplayOrDownload.aspx till UploadInDetailsView.aspx.

Kopiera och klistra in deklarativ syntax från DisplayOrDownload.aspx till UploadInDetailsView.aspx

Bild 3: Kopiera och klistra in deklarativ syntax från DisplayOrDownload.aspx till UploadInDetailsView.aspx (Klicka om du vill visa en bild i full storlek)

När du har kopierat den deklarativa syntaxen och GenerateBrochureLink metoden över till UploadInDetailsView.aspx sidan, öppna sidan i en webbläsare för att verifiera att allt kopierades korrekt. Du bör se en GridView som visar de åtta kategorier som innehåller en länk för att ladda ned broschyren samt kategorins bild.

Du bör nu se varje kategori tillsammans med dess binära data

Bild 4: Du bör nu se varje kategori tillsammans med dess binära data (klicka om du vill visa en bild i full storlek)

Steg 4: KonfigureraCategoriesDataSourcetill stöd för infogning

ObjectDataSource CategoriesDataSource som används av Categories GridView ger för närvarande inte möjlighet att infoga data. För att stödja infogning via den här datakällans kontroll måste vi mappa dess Insert metod till en metod i dess underliggande objekt, CategoriesBLL. I synnerhet vill vi mappa den till den CategoriesBLL metod som vi lade till i steg 2, InsertWithPicture.

Börja med att klicka på länken Konfigurera datakälla från den smarta taggen ObjectDataSource. Den första skärmen visar objektet som datakällan är konfigurerad för att fungera med, CategoriesBLL. Låt den här inställningen vara as-is och klicka på Nästa för att gå vidare till skärmen Definiera datametoder. Gå till fliken INSERT och välj InsertWithPicture metoden i listrutan. Klicka på Slutför för att slutföra guiden.

Konfigurera ObjectDataSource så att den använder InsertWithPicture-metoden

Bild 5: Konfigurera ObjectDataSource att använda InsertWithPicture metoden (Klicka om du vill visa en bild i full storlek)

Anmärkning

När du har slutfört guiden kan Visual Studio fråga om du vill uppdatera fält och nycklar, vilket återskapar fälten för datawebbkontroller. Välj Nej, eftersom om du väljer Ja skrivs eventuella fältanpassningar över.

När du har slutfört guiden innehåller ObjectDataSource nu ett värde för dess InsertMethod egenskap samt InsertParameters för de fyra kategorikolumnerna, som följande deklarativa markering illustrerar:

<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" 
    TypeName="CategoriesBLL" InsertMethod="InsertWithPicture">
    <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>
</asp:ObjectDataSource>

Steg 5: Skapa infogningsgränssnittet

Som först beskrivs i En översikt över infogning, uppdatering och borttagning av data tillhandahåller DetailsView-kontrollen ett inbyggt infogningsgränssnitt som kan användas när du arbetar med en datakällakontroll som stöder infogning. Låt oss lägga till en DetailsView-kontroll på den här sidan ovanför GridView som kommer att återge dess infogande gränssnitt permanent, så att en användare snabbt kan lägga till en ny kategori. När du lägger till en ny kategori i DetailsView uppdateras GridView under den automatiskt och visar den nya kategorin.

Börja med att dra en DetailsView från verktygslådan till designern ovanför GridView, ange dess ID egenskap till NewCategory och rensa egenskapsvärdena Height och Width . Från den smarta taggen DetailsView binder du den till den befintliga CategoriesDataSource och markerar sedan kryssrutan Aktivera infogning.

Skärmbild som visar DetailsView öppen med egenskapen CategoryID inställd på NewCategory, tomma egenskapsvärden för Höjd och Bredd och kryssrutan Aktivera infogning markerad.

Bild 6: Binda DetailsView till CategoriesDataSource och Aktivera infogning (Klicka om du vill visa en bild i full storlek)

Om du vill rendera DetailsView permanent i infogningsgränssnittet anger du dess DefaultMode egenskap till Insert.

Observera att DetailsView har fem BoundFields CategoryID, CategoryName, Description, NumberOfProductsoch BrochurePath även om CategoryID BoundField inte återges i infogningsgränssnittet eftersom dess InsertVisible egenskap är inställd på false. Dessa BoundFields finns eftersom de är kolumnerna som returneras av GetCategories() metoden, vilket är vad ObjectDataSource anropar för att hämta sina data. För infogning vill vi dock inte låta användaren ange ett värde för NumberOfProducts. Dessutom måste vi tillåta dem att ladda upp en bild för den nya kategorin samt ladda upp en PDF för broschyren.

Ta bort NumberOfProducts BoundField från DetailsView helt och hållet och uppdatera sedan HeaderText egenskaperna för BoundFields CategoryName och BrochurePath till Kategori respektive Broschyr. Konvertera sedan BrochurePath BoundField till ett TemplateField och lägg till ett nytt TemplateField för bilden, vilket ger det nya TemplateField värdet HeaderText Bild. Flytta Picture TemplateField så att det hamnar mellan BrochurePath TemplateField och CommandField.

Skärmbild av fältfönstret med TemplateField, Picture och HeaderText markerat.

Bild 7: Binda DetailsView till CategoriesDataSource och Aktivera infogning

Om du konverterade BrochurePath BoundField till ett TemplateField via dialogrutan Redigera fält innehåller TemplateField en ItemTemplate, EditItemTemplateoch InsertItemTemplate. Det är dock bara det InsertItemTemplate som behövs, så ta bort de andra två mallarna. I det här läget bör DetailsViews deklarativa syntax se ut så här:

<asp:DetailsView ID="NewCategory" runat="server" AutoGenerateRows="False" 
    DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource" 
    DefaultMode="Insert">
    <Fields>
        <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" 
            InsertVisible="False" ReadOnly="True" 
            SortExpression="CategoryID" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" />
        <asp:BoundField DataField="Description" HeaderText="Description" 
            SortExpression="Description" />
        <asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
            <InsertItemTemplate>
                <asp:TextBox ID="TextBox1" runat="server"
                    Text='<%# Bind("BrochurePath") %>'></asp:TextBox>
            </InsertItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Picture"></asp:TemplateField>
        <asp:CommandField ShowInsertButton="True" />
    </Fields>
</asp:DetailsView>

Lägga till FileUpload-kontroller för broschyr- och bildfälten

BrochurePath För närvarande innehåller TemplateField s InsertItemTemplate en textruta, medan Picture TemplateField inte innehåller några mallar. Vi måste uppdatera dessa två TemplateField s InsertItemTemplate för att använda FileUpload-kontroller.

I den smarta taggen DetailsView väljer du alternativet Redigera mallar och väljer BrochurePath sedan TemplateFields InsertItemTemplate i listrutan. Ta bort textrutan och dra sedan en FileUpload-kontroll från verktygslådan till mallen. Ange FileUpload-kontrollen s ID till BrochureUpload. På samma sätt lägger du till en FileUpload-kontroll i Picture TemplateField s InsertItemTemplate. Ställ in denna FileUpload-kontroll på IDPictureUpload.

Lägg till en FileUpload-kontroll i InsertItemTemplate

Bild 8: Lägg till en FileUpload-kontroll i InsertItemTemplate (Klicka om du vill visa en bild i full storlek)

När du har gjort dessa tillägg blir de två TemplateFields deklarativa syntaxer:

<asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
    <InsertItemTemplate>
        <asp:FileUpload ID="BrochureUpload" runat="server" />
    </InsertItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Picture">
    <InsertItemTemplate>
        <asp:FileUpload ID="PictureUpload" runat="server" />
    </InsertItemTemplate>
</asp:TemplateField>

När en användare lägger till en ny kategori vill vi se till att broschyren och bilden är av rätt filtyp. För broschyren måste användaren ange en PDF-fil. För bilden behöver vi att användaren laddar upp en bildfil, men tillåter vi alla bildfiler eller endast bildfiler av en viss typ, till exempel GIF-filer eller JPG:er? För att tillåta olika filtyper behöver vi utöka schemat så att det Categories innehåller en kolumn som samlar in filtypen så att den här typen kan skickas till klienten via Response.ContentType i DisplayCategoryPicture.aspx. Eftersom vi inte har en sådan kolumn är det klokt att begränsa användare till att endast tillhandahålla en viss bildfiltyp. Tabellens Categories befintliga bilder är bitmappar, men JPG:er är ett lämpligare filformat för bilder som hanteras via webben.

Om en användare laddar upp en felaktig filtyp måste vi avbryta infogningen och visa ett meddelande som anger problemet. Lägg till en etikettwebbkontroll under DetailsView. Ange egenskapen ID till UploadWarning, rensa dess Text egenskap, ange egenskapen CssClass till Varning och egenskaperna Visible och EnableViewState till false. CSS-klassen Warning definieras i Styles.css och renderar texten i ett stort, rött, kursivt, fetstilt teckensnitt.

Anmärkning

Idealiskt skulle CategoryName- och Description-BoundFields konverteras till TemplateFields och deras infogningsgränssnitt skräddarsys. Infogningsgränssnittet Description skulle till exempel förmodligen passa bättre genom en textruta med flera rader. Och eftersom CategoryName kolumnen inte accepterar NULL värden bör en RequiredFieldValidator läggas till för att säkerställa att användaren tillhandahåller ett värde för den nya kategorins namn. De här stegen lämnas som en övning för läsaren. Gå tillbaka till Anpassa datamodifieringsgränssnittet för en djupgående titt på hur du utökar gränssnitten för datamodifiering.

Steg 6: Spara den uppladdade broschyren till webbserverns filsystem

När användaren anger värdena för en ny kategori och klickar på knappen Infoga sker en återkoppling och infogningsarbetsflödet utvecklas. Först utlöses DetailsView-händelsenItemInserting. Därefter anropas metoden ObjectDataSources Insert() , vilket resulterar i att en ny post läggs till Categories i tabellen. Därefter utlöses DetailsView-händelsenItemInserted.

Innan metoden ObjectDataSources anropas Insert() måste vi först se till att lämpliga filtyper har laddats upp av användaren och sedan spara pdf-broschyren i webbserverns filsystem. Skapa en händelsehanterare för DetailsView-händelsen ItemInserting och lägg till följande kod:

// Reference the FileUpload control
FileUpload BrochureUpload = 
    (FileUpload)NewCategory.FindControl("BrochureUpload");
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;
        e.Cancel = true;
        return;
    }
}

Händelsehanteraren startar genom att BrochureUpload referera till FileUpload-kontrollen från DetailsView-mallarna. Om en broschyr sedan har laddats upp granskas filnamnstillägget för den uppladdade filen. Om tillägget inte är .PDF visas en varning, infogningen avbryts och körningen av händelsehanteraren avslutas.

Anmärkning

Att förlita sig på filnamnstillägget är inte en säker teknik för att säkerställa att den uppladdade filen är ett PDF-dokument. Användaren kan ha ett giltigt PDF-dokument med tillägget .Brochureeller ha tagit ett icke-PDF-dokument och gett det ett .pdf tillägg. Filens binära innehåll skulle behöva granskas programmatiskt för att mer slutgiltigt verifiera filtypen. Sådana grundliga metoder är dock ofta överdrivna; att kontrollera tillägget räcker för de flesta scenarier.

Som beskrivs i självstudien Ladda upp filer måste du vara försiktig när du sparar filer i filsystemet så att en användares uppladdning inte skriver över en annan fil. I den här handledningen försöker vi använda samma namn som den uppladdade filen. Om det redan finns en fil i ~/Brochures katalogen med samma filnamn lägger vi dock till ett tal i slutet tills ett unikt namn hittas. Om användaren till exempel laddar upp en broschyrfil med namnet Meats.pdf, men det redan finns en fil med namnet Meats.pdf i ~/Brochures mappen, ändrar vi det sparade filnamnet till Meats-1.pdf. Om det finns, provar vi Meats-2.pdf, och så vidare, tills ett unikt filnamn hittas.

Följande kod använder File.Exists(path) metoden för att avgöra om en fil redan finns med det angivna filnamnet. I så fall fortsätter den att prova nya filnamn för broschyren tills ingen konflikt hittas.

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++;
}

När ett giltigt filnamn har hittats måste filen sparas i filsystemet och värdet för ObjectDataSource måste uppdateras så att filnamnet skrivs brochurePath``InsertParameter till databasen. Som vi såg i självstudien Ladda upp filer kan filen sparas med hjälp av metoden FileUpload control s SaveAs(path) . Om du vill uppdatera parametern brochurePath för ObjectDataSource, använder du samlingen e.Values.

// Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath));
e.Values["brochurePath"] = brochurePath;

Steg 7: Spara den uppladdade bilden till databasen

För att lagra den uppladdade bilden i den nya Categories posten måste vi tilldela det uppladdade binära innehållet till parametern ObjectDataSources picture i DetailsView-händelsen ItemInserting . Innan vi gör den här tilldelningen måste vi dock först se till att den uppladdade bilden är en JPG och inte någon annan bildtyp. Precis som i steg 6 ska vi använda filnamnstillägget för den uppladdade bilden för att fastställa dess typ.

Categories Tabellen tillåter NULL värden för Picture kolumnen, men alla kategorier har för närvarande en bild. Låt oss tvinga användaren att tillhandahålla en bild när du lägger till en ny kategori via den här sidan. Följande kod kontrollerar att en bild har laddats upp och att den har ett lämpligt tillägg.

// Reference the FileUpload controls
FileUpload PictureUpload = (FileUpload)NewCategory.FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
    // 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;
        e.Cancel = true;
        return;
    }
}
else
{
    // No picture uploaded!
    UploadWarning.Text = 
        "You must provide a picture for the new category.";
    UploadWarning.Visible = true;
    e.Cancel = true;
    return;
}

Den här koden bör placeras före koden från steg 6 så att om det uppstår problem med bilduppladdningen avslutas händelsehanteraren innan broschyrfilen sparas i filsystemet.

Om du antar att en lämplig fil har laddats upp tilldelar du det uppladdade binära innehållet till bildparameterns värde med följande kodrad:

// Set the value of the picture parameter
e.Values["picture"] = PictureUpload.FileBytes;

Den fullständigaItemInsertinghändelsehanteraren

Här är ItemInserting händelsehanteraren i sin helhet för fullständighet:

protected void NewCategory_ItemInserting(object sender, DetailsViewInsertEventArgs e)
{
    // Reference the FileUpload controls
    FileUpload PictureUpload = (FileUpload)NewCategory.FindControl("PictureUpload");
    if (PictureUpload.HasFile)
    {
        // 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;
            e.Cancel = true;
            return;
        }
    }
    else
    {
        // No picture uploaded!
        UploadWarning.Text = 
            "You must provide a picture for the new category.";
        UploadWarning.Visible = true;
        e.Cancel = true;
        return;
    }
    // Set the value of the picture parameter
    e.Values["picture"] = PictureUpload.FileBytes;
    
    
    // Reference the FileUpload controls
    FileUpload BrochureUpload = 
        (FileUpload)NewCategory.FindControl("BrochureUpload");
    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;
            e.Cancel = true;
            return;
        }
        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));
        e.Values["brochurePath"] = brochurePath;
    }
}

Steg 8: Åtgärda sidanDisplayCategoryPicture.aspx

Låt oss ta en stund att testa infogningsgränssnittet och ItemInserting händelsehanteraren som skapades under de senaste stegen. Besök sidan UploadInDetailsView.aspx via en webbläsare och försök att lägga till en kategori, men utelämna bilden, eller ange en icke-JPG-bild eller en icke-PDF-broschyr. I något av dessa fall visas ett felmeddelande och infogningsarbetsflödet avbryts.

Ett varningsmeddelande visas om en ogiltig filtyp laddas upp

Bild 9: Ett varningsmeddelande visas om en ogiltig filtyp har laddats upp (Klicka om du vill visa en bild i full storlek)

När du har kontrollerat att sidan kräver att en bild laddas upp och inte accepterar icke-PDF- eller icke-JPG-filer lägger du till en ny kategori med en giltig JPG-bild och lämnar fältet Broschyr tomt. När du har klickat på knappen Infoga, kommer sidan att laddas om och en ny post läggs till i Categories-tabellen med den uppladdade bildens binära innehåll som lagras direkt i databasen. GridView uppdateras och visar en rad för den nyligen tillagda kategorin, men som bild 10 visar återges inte den nya kategoribilden korrekt.

Den nya kategorins bild visas inte

Bild 10: Den nya kategorins bild visas inte (Klicka om du vill visa en bild i full storlek)

Anledningen till att den nya bilden inte visas är att sidan DisplayCategoryPicture.aspx som returnerar en angiven kategoribild är konfigurerad för att bearbeta bitmappar som har en OLE-rubrik. Den här rubriken på 78 byte tas bort från Picture kolumnens binära innehåll innan de skickas tillbaka till klienten. Men JPG-filen som vi just laddade upp för den nya kategorin har inte den här OLE-rubriken; Därför tas giltiga nödvändiga byte bort från bildens binära data.

Eftersom det nu finns både bitmappar med OLE-huvuden och JPG:er i Categories tabellen måste vi uppdatera DisplayCategoryPicture.aspx så att ole-sidhuvudet rensas för de ursprungliga åtta kategorierna och kringgår den här borttagningen för de nyare kategoriposterna. I nästa handledning kommer vi att undersöka hur du uppdaterar en befintlig posts bild, och vi kommer att uppdatera alla gamla kategoribilder så att de är JPG:er. För tillfället använder du dock följande kod i DisplayCategoryPicture.aspx för att ta bort OLE-huvudena endast för de ursprungliga åtta kategorierna:

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];
    if (categoryID <= 8)
    {
        // For older categories, we must strip the OLE header... images are bitmaps
        // Output HTTP headers providing information about the binary data
        Response.ContentType = "image/bmp";
        // Output the binary data
        // But first we need to strip out the OLE header
        const int OleHeaderLength = 78;
        int strippedImageLength = category.Picture.Length - OleHeaderLength;
        byte[] strippedImageData = new byte[strippedImageLength];
        Array.Copy(category.Picture, OleHeaderLength, strippedImageData, 
            0, strippedImageLength);
        Response.BinaryWrite(strippedImageData);
    }
    else
    {
        // 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);
    }
}

Med den här ändringen återges JPG-avbildningen nu korrekt i GridView.

JPG-avbildningarna för nya kategorier återges korrekt

Bild 11: JPG-avbildningarna för nya kategorier återges korrekt (klicka om du vill visa en bild i full storlek)

Steg 9: Ta bort broschyren vid ett undantag

En av utmaningarna med att lagra binära data på webbserverns filsystem är att det medför en frånkoppling mellan datamodellen och dess binära data. När en post tas bort måste därför motsvarande binära data i filsystemet också tas bort. Detta kan också spela in när man infogar. Tänk på följande scenario: en användare lägger till en ny kategori och anger en giltig bild och broschyr. När du klickar på knappen Infoga inträffar en återkoppling och DetailsView-händelsen ItemInserting utlöses, vilket sparar broschyren till webbserverns filsystem. Sedan anropas metoden ObjectDataSources Insert() , som anropar CategoriesBLL metoden class s InsertWithPicture , som anropar CategoriesTableAdapter s-metoden InsertWithPicture .

Vad händer nu om databasen är offline eller om det finns ett fel i SQL-instruktionen INSERT ? Det är tydligt att INSERT misslyckas, så ingen ny kategorirad läggs till i databasen. Men vi har fortfarande den uppladdade broschyrfilen som sitter på webbserverns filsystem! Den här filen måste tas bort vid ett undantag under arbetsflödet för infogning.

Som tidigare beskrivits i självstudiekursen Hantera BLL- och DAL-Level undantag i en ASP.NET-sida , bubblas det upp genom de olika lagren när ett undantag genereras inifrån djupet av arkitekturen. På presentationsskiktet kan vi avgöra om ett undantag har inträffat från DetailsView-händelsen ItemInserted . Den här händelsehanteraren innehåller också värdena för ObjectDataSources InsertParameters. Därför kan vi skapa en händelsehanterare för händelsen ItemInserted som kontrollerar om det fanns ett undantag och i så fall tar bort filen som anges av parametern ObjectDataSource:brochurePath

protected void NewCategory_ItemInserted
    (object sender, DetailsViewInsertedEventArgs e)
{
    if (e.Exception != null)
    {
        // Need to delete brochure file, if it exists
        if (e.Values["brochurePath"] != null)
            System.IO.File.Delete(Server.MapPath(
                e.Values["brochurePath"].ToString()));
    }
}

Sammanfattning

Det finns ett antal steg som måste utföras för att tillhandahålla ett webbaserat gränssnitt för att lägga till poster som innehåller binära data. Om binära data lagras direkt i databasen är det sannolikt att du måste uppdatera arkitekturen och lägga till specifika metoder för att hantera fallet där binära data infogas. När arkitekturen har uppdaterats skapar nästa steg infogningsgränssnittet, som kan utföras med hjälp av en DetailsView som har anpassats för att inkludera en FileUpload-kontroll för varje binärt datafält. De uppladdade data kan sedan sparas i webbserverns filsystem eller tilldelas till en datakällparameter i Händelsehanteraren för DetailsView ItemInserting .

Att spara binära data i filsystemet kräver mer planering än att spara data direkt i databasen. Ett namngivningsschema måste väljas för att undvika att en användares uppladdning skriver över en annan. Dessutom måste du vidta extra åtgärder för att ta bort den uppladdade filen om databasinfogningen misslyckas.

Vi har nu möjlighet att lägga till nya kategorier i systemet med en broschyr och bild, men vi har ännu inte tittat på hur du uppdaterar en befintlig kategoris binära data eller hur du tar bort binära data korrekt för en borttagen kategori. Vi kommer att utforska de här två ämnena i nästa handledning.

Lycka till med programmerandet!

Om författaren

Scott Mitchell, författare till sju ASP/ASP.NET-böcker och grundare av 4GuysFromRolla.com, har arbetat med Microsofts webbtekniker sedan 1998. Scott arbetar som oberoende konsult, tränare och författare. Hans senaste bok är Sams Teach Yourself ASP.NET 2.0 på 24 timmar. Han kan nås på mitchell@4GuysFromRolla.com.

Särskilt tack till

Den här självstudieserien granskades av många användbara granskare. Huvudgranskare för den här handledningen var Dave Gardner, Teresa Murphy och Bernadette Leigh. Vill du granska mina kommande MSDN-artiklar? Om så är fallet, hör av dig på mitchell@4GuysFromRolla.com.