Jechoduchý MetaWeblog

3. díl - Jechoduchý MetaWeblog

Tomáš Herceg       6. 5. 2007       VB.NET, ASP.NET WebForms, XML, HTTP/HTML, I/O operace       8738 zobrazení

V tomto seriálu si ukážeme implementaci MetaWeblog API na velice jednochém blogu v ASP.NET. Podporovány jsou pouze základní funkce, ale ukážeme si zde veškerá úskalí, která nás při implementaci MetaWeblog API mohou potkat a pravděpodobně i potkají.

V minulém díle tohoto seriálu jsme si vysvětlili a podrobně popsali princip fungování MetaWeblog API, v tomto díle si napíšeme velice jednoduchý blog v ASP.NET, jehož obsah zpřístupníme pomocí tohoto systému. Nebudeme dělat žádné administrační rozhraní, to si může udělat každý sám, napíšeme jen čisté jádro. Každý si pak může aplikaci přizpůsobit k obrazu svému. Protože se chci vejít do jednoho článku, předpokládám od vás alespoň základní znalosti ASP.NET, jazyka SQL a Visual Basic .NET, bez toho se daleko nedostaneme. Pokud preferujete jazyk C#, vězte, že se není třeba ničeho obávat, Visual Basic má akorát jinou syntaxi, jinak je vše stejné. Pokud jste líní kód přepisovat, můžete využít konvertor.

Vytvoření databáze

Pro vytvoření databáze budete potřebovat nějaký program na správu MS SQL Serveru 2005 (doporučuji SQL Server Management Studio). Vytvořte si novou databázi (v tomto článku je její název SimpleBlog, pokud dáte název jiný, musíte jej na příslušných místech změnit) a spusťte na ní tyto příkazy, které vytvoří velmi jednoduchou strukturu: 

CREATE TABLE [Articles] (
 [ArticleId] INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
 [PublishedDate] DATETIME NOT NULL DEFAULT GETDATE(),
 [Title] VARCHAR(200) NOT NULL,
 [Html] TEXT NOT NULL,
 [Visible] BIT NOT NULL
)

CREATE TABLE [Categories] (
 [CategoryId] INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
 [Title] VARCHAR(200) NOT NULL
)
INSERT INTO [Categories] ([Title]) VALUES ('Databáze')
INSERT INTO [Categories] ([Title]) VALUES ('C#')
INSERT INTO [Categories] ([Title]) VALUES ('VB.NET')
INSERT INTO [Categories] ([Title]) VALUES ('Ostatní')

CREATE TABLE [ArticleCategories] (
 [ArticleCategoryId] INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
 [ArticleId] INT NOT NULL REFERENCES [Articles] ([ArticleId]) ON DELETE CASCADE,
 [CategoryId] INT NOT NULL REFERENCES [Categories] ([CategoryId]) ON DELETE CASCADE
)

Vytvořili jsme si tabulku Articles, která obsahuje články, tabulku Categories, která obsahuje kategorie, a vazebnou tabulku ArticleCategories, která obsahuje propojení článků s kategoriemi. Jeden článek je možné přiřadit do více kategorií, tabulka má nastaveno automatické odstranění záznamů v případě smazání článku nebo smazání kategorie.

Vytvoření ASP.NET WebSite

Vytvořte si novou ASP.NET WebSite (potřebujete Visual Studio 2005 nebo Visual Web Developer, který je zdarma) a odstraňte z ní soubor Default.aspx. Předpokládám, že budeme časem tomuto blogu přidávat nějaký vzhled, proto do projektu přidejte novou položku MasterPage a po vytvoření na ní pravým tlačítkem klikněte a zvolte Add Content Page. Tím se nám stránka Default.aspx vytvoří znovu, bude ale zaintegrována do MasterPage, takže ji můžeme jednoduše začlenit do layoutu stránky.

Otevřete editor kódu, vše z něj vymažte a vložte tam tento:

<%@ Page Language="VB" MasterPageFile="~/MasterPage.master" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" title="SimpleBlog" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:SimpleBlogCS %>" 
        SelectCommand="SELECT [ArticleId], [PublishedDate], [Title] FROM [Articles] ORDER BY [PublishedDate] DESC"></asp:SqlDataSource>
    <br />
    <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" AutoGenerateColumns="false">
        <Columns>
            <asp:HyperLinkField DataTextField="Title" HeaderText="Článek" DataNavigateUrlFields="ArticleId" DataNavigateUrlFormatString="Article.aspx?id={0}" ItemStyle-Font-Bold="true" />
            <asp:BoundField DataField="PublishedDate" HeaderText="Publikováno" HtmlEncode="false" DataFormatString="{0:D}" ItemStyle-Width="20%" />
        </Columns>
    </asp:GridView>
</asp:Content>

GridView teď bude mít dva sloupce, první bude obsahovat název článku a bude odkazovat na adresu Article.aspx?id={0}, kde za {0} se doplní ID článku. Druhý sloupeček bude obsahovat datum publikování článku. 

Tím jsme zvládli úplně základní výpis článků. Samozřejmě je na místě, abyste si změnili vzhled aplikace, vytvořili téma, ale o tom tento článek není. To, co je důležité, teprve přijde.

Zobrazení článku

Do projektu si přidejte další stránku, pojmenujte ji Article.aspx a zvolte Select Master Page, abyste mohli vybrat, kam chcete tuto stránku začlenit. Opět si otevřete okno kódu a místo vygenerovaného kódu tam vložte tento:

<%@ Page Language="VB" MasterPageFile="~/MasterPage.master" Title="Článek v blogu" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:SimpleBlogCS %>"
        SelectCommand="SELECT [PublishedDate], [Title], [Html] FROM [Articles] WHERE ([ArticleId] = @ArticleId)">
        <SelectParameters>
            <asp:QueryStringParameter Name="ArticleId" QueryStringField="id" Type="Int32" />
        </SelectParameters>
    </asp:SqlDataSource>
    <br />
    <asp:FormView ID="FormView1" runat="server" DataSourceID="SqlDataSource1">
        <ItemTemplate>
            <h1><asp:Literal ID="Literal1" runat="server" Text='<%# Eval("Title") %>'></asp:Literal> </h1>
            <p>Publikováno: <strong><asp:Literal ID="Literal2" runat="server" Text='<%# Eval("PublishedDate", "{0:D}") %>'></asp:Literal></strong></p>
            <p>Kategorie: 
                <asp:SqlDataSource ID="SqlDataSource2" runat="server" ConnectionString="<%$ ConnectionStrings:SimpleBlogCS %>"
                SelectCommand="SELECT [Title] FROM [Categories] WHERE [CategoryId] IN (SELECT [CategoryId] FROM [ArticleCategories] WHERE [ArticleId] = @ArticleId)">
                    <SelectParameters>
                        <asp:QueryStringParameter Name="ArticleId" QueryStringField="id" Type="Int32" />
                    </SelectParameters>
                </asp:SqlDataSource>
                <asp:Repeater ID="Repeater1" runat="server" DataSourceID="SqlDataSource2">
                    <ItemTemplate><strong><%#Eval("Title") %></strong></ItemTemplate>
                    <SeparatorTemplate>, </SeparatorTemplate>
                </asp:Repeater>
            </p>
            <hr />
            <p><asp:Literal ID="Literal3" runat="server" Text='<%# Eval("Html") %>'></asp:Literal> </p>
            <p style="text-align: center"><asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">[Home]</asp:HyperLink></p>
        </ItemTemplate>
    </asp:FormView>
</asp:Content>

Přidali jsme na stránku komponentu FormView, která zobrazí název článku jako nadpis 1. úrovně a dále vypíše datum publikování a text článku. Nakonec je odkaz na úvodní stránku blogu. Uvnitř je ještě jeden datový zdroj a komponenta Repeater, která vypisuje názvy kategorií přidružené k článku.

Vytváříme publikační službu

Ještě než do projektu přidáme webovou službu pro publikování článků, musíme si uvědomit, že komunikace probíhá pomocí XML-RPC, které .NET sám o sobě neumí. Buď si můžeme napsat vlastní handler na zpracovávání takových požadavků, nebo můžeme využít Xml-Rpc.net, což je projekt, který umožní vytvořit XML-RPC volanou webovou službu. Toto řešení je elegantnější a jednodušší, takže se jej budeme držet.

Stáhněte si tedy XML-RPC knihovnu a přidejte si ji do adresáře Bin do vaší webové aplikace. Raději jsem ji zkompiloval a nahrál sem, bude to pro vás jednodušší.

Nyní si do projektu přidejte nový Generic Handler MetaWeblog.ashx, který bude spouštět XML-RPC volané procedury. Dá se použít i webová služba, ale musí se zasahovat do konfigurace aplikace. Handler je jednodušší a pro naše účely plně postačuje.

Postupně budeme implementovat veškeré procedury, které budeme potřebovat. Zatím do handleru vložte tyto deklarace:

 

<%@ WebHandler Language= "VB" Class="MetaWeblog" %>
<%@ Assembly Name="CookComputing.XmlRpcV2" %>

Imports CookComputing.XmlRpc
Imports System.Data.SqlClient

<XmlRpcService()> _
Public Class MetaWeblog
    Inherits XmlRpcService
    
    Public Structure BlogInfo
        Public blogid As String
        Public url As String
        Public blogName As String
    End Structure

    <XmlRpcMissingMapping(MappingAction.Ignore)> _
    Public Structure Enclosure
        Public length As Integer
        Public type As String
        Public url As String
    End Structure

    <XmlRpcMissingMapping(MappingAction.Ignore)> _
    Public Structure Source
        Public name As String
        Public url As String
    End Structure

    <XmlRpcMissingMapping(MappingAction.Ignore)> _
    Public Structure Post
        <XmlRpcMissingMapping(MappingAction.Error)> Public dateCreated As DateTime
        <XmlRpcMissingMapping(MappingAction.Error)> Public description As String
        <XmlRpcMissingMapping(MappingAction.Error)> Public title As String
        Public categories As String()
        Public link As String
        Public permalink As String
        Public postid As Object
    End Structure

    Public Structure CategoryInfo
        Public description As String
        Public title As String
        Public categoryid As String
    End Structure

    Public Structure Category
        Public categoryId As String
        Public categoryName As String
    End Structure

    Public Structure FileData
        Public bits As Byte()
        Public name As String
        Public type As String
    End Structure

    Public Structure UrlData
        Public url As String
    End Structure 


 
''' <summary> ''' Ověřit, jestli má uživatel přístup k serveru ''' </summary> Private Function ValidateUser(ByVal username As String, ByVal password As String) As Boolean If (username = "tomas" And password = "tomas") Then
            Return True
Else
Throw New XmlRpcException("Invalid username or password!")
End If End Function
''' <summary> ''' Ověřit, jestli má uživatel přístup k blogu ''' </summary> Private Function ValidateUserAgainstBlog(ByVal username As String, ByVal password As String, ByVal blogid As String) As Boolean Return ValidateUser(username, password) End Function ''' <summary> ''' Ověřit, jestli má uživatel přístup k příspěvku ''' </summary> Private Function ValidateUserAgainstPost(ByVal username As String, ByVal password As String, ByVal postid As String) As Boolean Return ValidateUser(username, password) End Function Private con As SqlConnection Private com As SqlCommand ''' <summary> ''' Připojí se k databázi a inicializuje SqlCommand ''' </summary> Private Sub DBConnect() con = New SqlConnection(ConfigurationManager.ConnectionStrings("SimpleBlogCS").ConnectionString) con.Open() com = New SqlCommand() com.Connection = con End Sub ''' <summary> ''' Ukončí spojení s databází ''' </summary> Private Sub DBClose() con.Close() End Sub ''' <summary> ''' Vrátí absolutní URL kořenu webové aplikace ''' </summary> Private Function GetAbsoluteApplicationPath() As String Dim url As String = HttpContext.Current.Request.Url.AbsoluteUri Return url.Substring(0, url.LastIndexOf("/") + 1) End Function ' ------------------------------------------- PROCEDURY METAWEBLOG API ------------------------------------------- End Class

 Nás bude nyní zajímat třída MetaWeblog. Nahoře máme deklarace struktur, které se v MetaWeblog API používají, abychom je měli silně typované. Kromě toho jsem doplnil procedury ValidateUser, ValidateUserAgainstBlog a ValidateUserAgainstPost, které kontrolují oprávnění uživatele. Nepoužívám zde kvůli jednoduchosti žádné ASP.NET přihlašování, jen zkontroluji jméno a heslo (standardně jméno tomas a heslo tomas, změňte si dle libosti). První funkce kontroluje, jestli uživatel existuje a jestli má správné heslo, druhá kontroluje, jestli má uživatel přístup do daného blogu a třetí kontroluje, jestli může uživatel manipulovat s daným příspěvkem. Druhá a třetí by zároveň měly volat tu první, která ověří správnost hesla. V každé proceduře se totiž volá jen jedna z těchto funkcí.

Dále jsem vytvořil proměnné con (SqlConnection) a com (SqlCommand). Důležitý je hlavně com, pomocí kterého se budeme dotazovat databáze a posílat do ní příkazy. Navíc jsem řidal dvě procedury DbConnect a DBClose, které by se měly zavolat na začátku a na konci procedury (na začátku se k databázi připojíme a na konci se od ní odpojíme). První z nich připraví spojení a inicializuje com, který pak můžeme používat.

Poslední funkci, kterou jsem připravil, je GetAbsoluteApplicationPath, která vrátí absolutní URL kořenového adresáře aplikace, kterou budeme na několika místech potřebovat.

Implementace jednotlivých procedur

V této části najdete poznámky a kód každé z procedur, kterou byste měli implementovat. Všechny procedury umístěte do třídy v našem handleru.

blogger.GetUsersBlogs

Tato procedura vrátí všechny blogy, ke kterým má uživatel přístup. Jelikož v naší primitivní aplikaci podporujeme pouze jeden blog, vypíšeme jej natvrdo. Parametr url struktury BlogInfo je absolutní URL blogu (např. www.mujblog.cz).

    <XmlRpcMethod("blogger.getUsersBlogs")> _
    Function getUsersBlogs(ByVal appKey As String, ByVal username As String, ByVal password As String) As BlogInfo()
        ValidateUser(username, password)

        'vrátit uživatelovy blogy
        Dim blogs(0) As BlogInfo
        blogs(0).blogid = 1
        blogs(0).blogName = "SimpleBlog"
        blogs(0).url = GetAbsoluteApplicationPath()
             
        Return blogs
    End Function

blogger.deletePost

Tato procedura vymaže článek v blogu. Vzdálené klíče v naší databázi jsou nastaveny tak, aby se automaticky zrušily vazby v tabulce ArticleCategories, nemusíme se tedy o nic starat, zkrátka jen smažeme článek. Funkce vrací vždy hodnotu True.

    <XmlRpcMethod("blogger.deletePost")> _
    Function deletePost(ByVal appKey As String, ByVal postid As String, ByVal username As String, ByVal password As String, ByVal publish As Boolean) As Boolean
        ValidateUserAgainstPost(username, password, postid)
        DBConnect()

        'odstranit článek
        com.CommandText = "DELETE FROM [Articles] WHERE [ArticleId] = @ArticleId"
        com.Parameters.AddWithValue("ArticleId", postid)
        com.ExecuteNonQuery()
        
        DBClose()
        Return True
    End Function

metaWeblog.newPost

Tato procedura přidá nový článek do blogu. Nejprve tedy vytvoříme nový záznam v tabulce Articles a rovnou zjistíme ArticleId nově vytvořeného článku, protože jej budeme potřebovat pro přiřazení kategorií. Je to také návratová hodnota této funkce. Navíc musíme provázat kategorie s článkem. Problém může nastat s parametrem dateCreated. Pokud datum v klientské aplikaci zvolíme ručně, pošle se a vše je v pořádku. Pokud ale datum v klientovi nevyplníme, parametr se nám nezinicializuje a datum ukazuje na 1.1.0001, což se nelíbí databázi. Pokud je tedy rok menší než 1900, je jasné, že se datum nezinicializovalo a nastavíme je na Now, tedy aktuální čas.

    <XmlRpcMethod("metaWeblog.newPost")> _
    Function newPost(ByVal blogid As String, ByVal username As String, ByVal password As String, ByVal post As Post, ByVal publish As Boolean) As String
        ValidateUserAgainstBlog(username, password, blogid)
        DBConnect()

        'přidat článek a ihned zjistit jeho ID
        com.CommandText = "INSERT INTO [Articles] ([Title], [Html], [Visible], [PublishedDate]) VALUES (@Title, @Html, @Visible, @PublishedDate); SELECT SCOPE_IDENTITY()"
        If post.dateCreated.Year < 1900 Then post.dateCreated = Now 'datum není inicializován
        com.Parameters.AddWithValue("PublishedDate", post.dateCreated)
        com.Parameters.AddWithValue("Title", post.title)
        com.Parameters.AddWithValue("Html", post.description)
        com.Parameters.AddWithValue("Visible", publish)
        Dim postid As String = com.ExecuteScalar().ToString()
        
        'přidat nové vazby na kategorie
        For Each cat As String In post.categories
            com.Parameters.Clear()
            com.CommandText = "DECLARE @CategoryId INT; SELECT @CategoryId = [CategoryId] FROM [Categories] WHERE [Title] = @Title; INSERT INTO [ArticleCategories] ([ArticleId], [CategoryId]) VALUES (@ArticleId, @CategoryId)"
            com.Parameters.AddWithValue("ArticleId", postid)
            com.Parameters.AddWithValue("Title", cat)
            com.ExecuteNonQuery()
        Next
        
        DBClose()
        Return postid
    End Function

metaWeblog.editPost

Tato procedura upraví článek. Po editaci smažeme všechny vazby, které se k tomuto článku vztahují, projdeme pole categories a kategorie přidáme. Je to asi jednodušší než zjistit všechny přidružené kategorie a nové přidat a přebývající vymazat. Funkce vrací vždy hodnotu True. O datu platí to samé, co u přechozí procedury. Musíme dát pozor, aby nezůstalo nezinicializované, databáze to nevezme.

    <XmlRpcMethod("metaWeblog.editPost")> _
    Function editPost(ByVal postid As String, ByVal username As String, ByVal password As String, ByVal post As Post, ByVal publish As Boolean) As Boolean
        ValidateUserAgainstPost(username, password, postid)
        DBConnect()

        'upravit článek
        com.CommandText = "UPDATE [Articles] SET [Title] = @Title, [Html] = @Html, [Visible] = @Visible, [PublishedDate] = @PublishedDate WHERE [ArticleId] = @ArticleId"
        com.Parameters.AddWithValue("ArticleId", postid)
        com.Parameters.AddWithValue("Title", post.title)
        com.Parameters.AddWithValue("Html", post.description)
        com.Parameters.AddWithValue("Visible", publish)
        If post.dateCreated.Year < 1900 Then post.dateCreated = Now 'datum není inicializován
        com.Parameters.AddWithValue("PublishedDate", post.dateCreated)
        com.ExecuteNonQuery()
        
        'smazat stávající vazby na kategorie
        com.Parameters.Clear()
        com.CommandText = "DELETE FROM [ArticleCategories] WHERE [ArticleId] = @ArticleId"
        com.Parameters.AddWithValue("ArticleId", postid)
        com.ExecuteNonQuery()
        
        'přidat nové vazby na kategorie
        For Each cat As String In post.categories
            com.Parameters.Clear()
            com.CommandText = "DECLARE @CategoryId INT; SELECT @CategoryId = [CategoryId] FROM [Categories] WHERE [Title] = @Title; INSERT INTO [ArticleCategories] ([ArticleId], [CategoryId]) VALUES (@ArticleId, @CategoryId)"
            com.Parameters.AddWithValue("ArticleId", postid)
            com.Parameters.AddWithValue("Title", cat)
            com.ExecuteNonQuery()
        Next
        
        DBClose()
        Return True
    End Function

metaWeblog.getCategories

Tato procedura vrátí všechny kategorie článků na blogu. Nejjednodušší je vytvořit si objekt List(Of CategoryInfo), což je silně typovaný seznam, vybrat kategorie z databáze a metodou Add je do seznamu přidat. Procedura musí vrátit pole, které ze seznamu dostaneme zavoláním metody ToArray. Doporučuji nastavit kategoriím title i description stejné, Windows Live Writer totiž v seznamu kategorií zobrazuje title, kdežto Word 2007 zobrazuje description.

    <XmlRpcMethod("metaWeblog.getCategories")> _
    Function getCategories(ByVal blogid As String, ByVal username As String, ByVal password As String) As CategoryInfo()
        ValidateUserAgainstBlog(username, password, blogid)
        DBConnect()

        'vypsat všechny kategorie        
        com.CommandText = "SELECT [CategoryId], [Title] FROM [Categories]"
        Dim r As SqlDataReader = com.ExecuteReader()
        Dim cats As New Collections.Generic.List(Of CategoryInfo)
        While r.Read
            Dim cat As New CategoryInfo
            cat.categoryid = r("CategoryId")
            cat.title = r("Title")
            cat.description = r("Title")
            cats.Add(cat)
        End While
        r.Close()
        
        DBClose()
        Return cats.ToArray()
    End Function

metaWeblog.getPost

Tato procedura vrátí kompletní informace o daném článku. Vybereme je tedy ze seznamu a podobným způsobem jako v předchozí proceduře naplníme i pole kategorií. Jediný problém je v tom, že Windows Live Writer název článku při odesílání provede HTMLEncode, což nahrazuje speciální znaky pomocí ekvivalentních HTML přepisů, ale zřejmě to provede dvakrát, takže musíme zavolat HTMLDecode, aby se přepisy převedly zpět, jinak jsou ve Wordu 2007 zobrazeny chybně.

    <XmlRpcMethod("metaWeblog.getPost")> _
    Function getPost(ByVal postid As String, ByVal username As String, ByVal password As String) As Post
        ValidateUserAgainstPost(username, password, postid)
        DBConnect()

        'získat data o článku
        com.CommandText = "SELECT [Title], [Html], [PublishedDate], [Visible] FROM [Articles] WHERE [ArticleId] = @ArticleId"
        com.Parameters.AddWithValue("ArticleId", postid)
        Dim p As New Post
        Dim r As SqlDataReader = com.ExecuteReader()
        r.Read()
        p.title = HttpUtility.HtmlDecode(r("Title"))
        p.description = r("Html")
        p.postid = postid
        p.dateCreated = r("PublishedDate")
        p.permalink = GetAbsoluteApplicationPath() & "Article.aspx?id=" & postid
        p.link = p.permalink
        r.Close()
        
        'naplnit kategorie
        com.Parameters.Clear()
        com.CommandText = "SELECT [Title] FROM [Categories] WHERE [CategoryId] IN (SELECT [CategoryId] FROM [ArticleCategories] WHERE [ArticleId] = @ArticleId)"
        com.Parameters.AddWithValue("ArticleId", postid)
        Dim cats As New Collections.Generic.List(Of String)
        Try
            r = com.ExecuteReader()
        Catch
            MsgBox(Err.Description)
        End Try
        While r.Read()
            cats.Add(r("Title"))
        End While
        p.categories = cats.ToArray()
        r.Close()
                
        DBClose()
        Return p
    End Function

metaWeblog.getRecentPosts

Tato procedura vrací několik posledních příspěvků v blogu (počet si klient zvolí). Zde je to velmi podobné předchozí proceduře, samozřejmě je i problém se zakódováním speciálních znaků, nemusíme zde však složitě vypisovat kategorie, aplikace je podle této procedury nezjišťuje, zavolá si getPost.

    <XmlRpcMethod("metaWeblog.getRecentPosts")> _
    Function getRecentPosts(ByVal blogid As String, ByVal username As String, ByVal password As String, ByVal numberOfPosts As Integer) As Post()
        ValidateUserAgainstBlog(username, password, blogid)
        DBConnect()
        
        'získat několik posledních článků
        com.CommandText = "SELECT TOP (@Count) [ArticleId], [Title], [Html], [PublishedDate], [Visible] FROM [Articles] ORDER BY [PublishedDate] DESC"
        com.Parameters.AddWithValue("Count", numberOfPosts)
        Dim posts As New Collections.Generic.List(Of Post)
        Dim r As SqlDataReader = com.ExecuteReader()
        While r.Read()
            Dim p As New Post
            p.title = HttpUtility.HtmlDecode(r("Title"))
            p.description = r("Html")
            p.postid = r("ArticleId")
            p.dateCreated = r("PublishedDate")
            p.permalink = GetAbsoluteApplicationPath() & "Article.aspx?id=" & r("ArticleId")
            p.link = p.permalink
            posts.Add(p)
        End While
        r.Close()
                
        DBClose()
        Return posts.ToArray()
    End Function

metaWeblog.newMediaObject

Poslední a nejproblematičtější procedurou je tato. Pomocí ní se totiž nahrávají na blog soubory. Podle mě je ale navržená poněkud nešťastně, protože soubory se váží na celý blog a nikoliv na článek. Server v skutečnosti neví, ke kterému článku soubor patří, posílá se totiž jen ID blogu a název souboru. Pokud soubor s daným názvem již existuje, má být podle specifikace přepsán, což je ale další problém. Představte si situaci, kdy máte dva články a v každém je úplně jiný obrázek, který se však jmenuje stejně. První článek odešlete a v okamžiku, kdy odešlete ten druhý, soubor má stejný název a tím pádem se přepíše. Server totiž neví, jestli upravuje obrázek již existujícího článku, nebo jestli je tento obrázek k článku novému. A nemá ani šanci to zjistit, protože obrázky se posílají před přidáním nebo editací článku, ten ani nemusí mít ještě přiřazené id.

Word 2007 tento problém řeší elegantně - do názvu souboru přibalí aktuální datum a čas. To ale také není ideální, protože pokud článek upravíte a pošlete znovu, jméno souboru se změní a starý obrázek na serveru zůstane. Tohle děláte měsíc a máte server plný zbytečných obrázků. Jediná možnost je mít databázi vazeb souborů a článků a při přidání nebo editaci článku parsovat HTML a najít soubory, na které odkazuje, ty si uložit do databáze a při editaci nebo smazání článku přidružené soubory vymazat. Ne, že by šlo o neřešitelný problém, parsování je poměrně jednoduché, pokud použijete třeba HTMLAgilityPack, ovšem je to obcházení špatně navrženého standardu. Windows Live Writer si většinu příspěvků uchovává lokálně, takže zná jména souborů, ale pokud blog spravujeme z jiných počítačů, soubor se přepsat může. 

My se s tímto zatím trápit nebudeme, možná v některém z příštích článků, ale máme zde ještě jeden problém. První parametr této procedury je blogid, který má být typu StringWord 2007 má v implementaci této procedury chybu a parametr blogid posílá jakotyp Object a protože většina databází má primární klíče typu Integer, pošle se parametr jako číslo a naše XMLRpc knihovna se s tím nevyrovná. Jediným řešením je implementovat tuto proceduru dvakrát, jednou pro String a jednou pro Object, přičemž ta druhá převede blogid na String a zavolá tu první.

Do projektu si musíte ještě přidat složku Files, do které budeme ukládat přiložené soubory. 

Tato procedura vrací absolutní URL k souboru, aby si aplikace mohla obrázek stáhnout.

    <XmlRpcMethod("metaWeblog.newMediaObject")> _
    Function newMediaObject(ByVal blogid As String, ByVal username As String, ByVal password As String, ByVal file As FileData) As UrlData
        ValidateUserAgainstBlog(username, password, blogid)

        'nahrát soubor do složky Files
        file.name = io.Path.GetFileName(file.name)
        Dim sw As New IO.FileStream(IO.Path.Combine(HttpContext.Current.Request.PhysicalApplicationPath, "Files\" & file.name), IO.FileMode.Create)
        sw.Write(file.bits, 0, file.bits.Length)
        sw.Close()
        Dim u As New UrlData
        u.url = GetAbsoluteApplicationPath() & "files/" & file.name
        
        Return u
    End Function
    
    <XmlRpcMethod("metaWeblog.newMediaObject")> _
    Function newMediaObject(ByVal blogid As Object, ByVal username As String, ByVal password As String, ByVal file As FileData) As UrlData
        Return newMediaObject(blogid.ToString(), username, password, file)
    End Function

Dokončení

To by mělo být celé. Můžete si zkusit nainstalovat aplikaci Windows Live Writer nebo vyzkoušet publikování ve Wordu 2007, jako typ blogovací služby musíte vybrat MetaWeblog API a adresa služby je adresa na náš handler (Live Writer se nejprve ptá na adresu blogu a pak až na adresu služby). Publikační službu si samozřejmě upravte tak, aby vám vyhovovala a aby pracovala s vaší databází. Protože tyto záležitosti se špatně ladí, doporučuji použít neocenitelný nástroj Fiddler pro sledování komunikace.

 Úvodní stránka blogu

Příspěvek psaný ve Windows Live Writeru

Příspěvek psaný ve Wordu 2007 

Jinak poslední moje upozornění je, že Word 2007 posílá krásné čisté XHTML (ne ty hrůzy jako předchozí verze), zatímco Live Writer se neobtěžuje a posílá jen HTML (neuzavírá odstavce), takže s XHTML validním webem se můžete rozloučit, pokud nechcete řešit konverzi na XHTML, která ovšem nefunguje vždy tak, jak by měla. Nechápu, proč negeneruje XHTML rovnou, ani starým prohlížečům to nevadí (a přiznejme si, kdo dnes používá Netscape) a mobilní zařízení se s tím poperou také velmi dobře.

 

hodnocení článku

2 bodů / 2 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Všechny díly tohoto seriálu

3. Jechoduchý MetaWeblog 6. 5. 2007
2. Princip MetaWeblog API 26. 4. 2007
1. Snadnější publikování na webu 26. 4. 2007

 

Mohlo by vás také zajímat

Windows Presentation Foundation (WPF) - díl 4.: Architektura a objektový model WPF

Na jaké kompromisy museli architekti WPF frameworku přistoupit, aby nabídli vývojářům pohodlný vývoj ve vyšších programovacích jazycích a zároveň odpovídající výkon výsledného uživatelského prostředí? Tento článek se věnuje architektuře WPF frameworku.

Práce s časovými pásmy a letním časem v aplikaci a databázi - díl 1.: Úvod do časových pásem a letního času

Ve článku se snažím popsat úskalí, která přináší konverze času přes více časových pásem a pravidel pro počítání letního času.

Hledáme .NET vývojáře (Praha, Brno, Frýdek-Místek)

 

 

Nový příspěvek

 

Diskuse: Jechoduchý MetaWeblog

pardon , ale nefunguje link na stiahnutie projektu prosim o opravu dakujem :)

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Jechoduchý MetaWeblog

Článek by si zasloužil pokračování na téma Rozšíření standardní funkčnosti pomocí zásuvných modulů. Zvažujete zde zveřejnit článek popisující vytvoření nějakého jednoduchého plug-inu?

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Jechoduchý MetaWeblog

Pěkný článek, jen mám problém s nastavení Live Writeru. Nikdy jsem s nim nepracoval. Nastavuji typ Metaweblog API a url http://localhost:8037/MetaWeblog.ashx, kde jsou naimplementované potřebné metody. Píše chybu "Neplatná odpověď na blogger.getUsersBlogs. Object reference not set to instance...". Přitom metody vrací správné výsledky, mám to otestované. Je problém v tom, že to není veřejná URL? Díky.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Nemůže to vracet správné výsledky, když ta metoda evidentně hlásí Live Writeru NullReferenceException. Musíte tam někde mít chybu. Pokud přesto metoda funguje, pak bude chyba v handleru a napojení na XmlRpc knihovnu. Existuje ta metoda GetUsersBlogs? Má přesně stejný název? Je správně oatributovaná?

S URL to nemá nic do činění, Live Writer samozřejmě skousne i lokální adresy.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Tak je vše v pořádku, zapoměl jsem na jeden atribut...

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Jechoduchý MetaWeblog

Potřebuji tu databázi ke stažení.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Já bych toho potřeboval...

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Jechoduchý MetaWeblog

Děkuji za tenoto článek, jen se mi zdá že nefungují katerorie - mám Window Live Writer verze 2008 a nikde není na výber kategorie(jako je na screenshotech v článku)

PS:kód je převáděný do C#

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Podívejte se pořádně, co máte za verzi. Windows Live Writer nemá žádnou verzi 2008!

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

V about dialogu je :

Version 2008 (Build 12.0.1366.1026) en

(někdy předvčera stahovaný)

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Máte pravdu, i mě to píše version 2008. Je to zvláštní, všude píší, že to je Windows Live Writer 1.0, to mě zmátlo. Panel s kategoriemi by měl být dole, v době psaní tohoto článku existovala jen verze Beta 1, která měla seznam kategorií nahoře.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

jj Díky jinak bezproblému(jen jsme si toho nějak nevšim)

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Jechoduchý MetaWeblog

Zda se mi, ze negunguje odkaz na stazeni XML-RPC knihovny

a zaroven ani odkaz na "Stáhnout webovou aplikaci"

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Děkuji za upozornění, chybu jsem opravil.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Zkousim ten priklad, ale nejak se mi nedari to ulozit pokud jsou v clanku obrazky, neni potreba nastavit neco extra ve wordu nebo na IIS?

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Co to hlásí za chybu?

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Toto se tam ukaze:

Aplikace Word nemůže publikovat obrázky v tomto příspěvku. Váš zprostředkovatel s největší pravděpodobností nepodporuje ukládání obrázků.

Obraťte se na zprostředkovatele a opakujte akci, nebo zvolte jiného zprostředkovatele obrázků.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

tak vyreseno opravdu je to jen o pravech na slozce je potreba mit povolen zapis na ucte IIS_WPG

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Jechoduchý MetaWeblog

V kódu je bezpečnostní díra -- sice poctivě voláš metodu ValidateUser(), ale pokud uživatel není validní, nic se neděje, metoda jenom vrátí false a jede se dál.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Děkuji za upozornění, chybu jsem opravil.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Jechoduchý MetaWeblog

Rád bych se pozeptal, je-li programátorsky možné po insertu nového záznamu stoupnout si v GridView na tento nový záznam, když mám v GridView povolené stránkování a nově vložený záznam je někde mimo rozsah právě zobrazené Page. Zkoušel jsem hledat v MSDN ale nic jsem nenašel.

V případě odpovědi dík.

Radek Kratochvil

nahlásit spamnahlásit spam 3 / 3 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback