Azure のテーブル ストレージ を ASP.NET MVC で使ってみる(その2)

前回の記事で簡単なメッセージ表示を行うものを作成したものの続きです。今回は、メッセージ更新の際にメッセージ作成者が決めたキーワードを確認するように変更を行い、構築済みの Windows Azure のテーブル ストレージに対してデータ項目の追加を行ったときの振る舞いを確認してみます。

確認した結果は次のとおりです。

  • データ項目の追加は問題なく行える。
  • データ項目を追加する前に作成されていたレコードにアクセスし、追加した項目を読み取ると「null」が得られる。
  • データ項目を追加する前に作成されていたレコードにアクセスし、追加した項目に対してデータを設定することも問題なくできる。

確認したときのスクリーンショットは次のとおり。

項目追加後にデータ追加を行ったレコードの確認

既存レコードの読み取りの確認

※3点目は IMessage インターフェイスに「備考」欄を追加することで確認しました(冗長になるのでコードとスクリーンショットは載せていません)。

ちなみに、画面はこんな感じになっています。

メッセージ記入画面

メッセージ修正画面

メッセージ修正時にキーワードが一致しなかったときの表示

メッセージ削除画面

プログラムの変更内容は以下のとおりです。
まずはモデルの変更になりますが、プロパティをインターフェイスで規定しているので、「Models」フォルダの「IMessage.cs」から変更していきます。プロパティに「UpdateKey」を追加します。

using System.ComponentModel.DataAnnotations;

namespace MvcWebRole1.Models
{
    public interface IMessage
    {
        [Display(Name = "メッセージ")]
        string MessageBody { get; set; }
        [Display(Name = "ユーザー名")]
        string UserName { get; set; }
        [Display(Name = "修正用のキーワード")]
        string UpdateKey { get; set; }

        string PartitionKey { get; set; }
        string RowKey { get; set; }
    }
}

次に同フォルダの「Message.cs」を変更します。プロパティに「UpdateKey」を追加します。

namespace MvcWebRole1.Models
{
    public class Message : IMessage
    {
        public string MessageBody { get; set; }
        public string UserName { get; set; }

        public string UpdateKey { get; set; }

        public string PartitionKey { get; set; }
        public string RowKey { get; set; }
    }
}

次にデータアクセス部分を変更します。

まずは テーブル ストレージのエンティティから。「DAL」フォルダの「MessageEntity.cs」を変更します。プロパティに「UpdateKey」を追加します。

using System;
using Microsoft.WindowsAzure.StorageClient;
using MvcWebRole1.Models;

namespace MvcWebRole1.DAL
{
    public class MessageEntity : TableServiceEntity, IMessage
    {
        public string MessageBody { get; set; }
        public string UserName { get; set; }

        public string UpdateKey { get; set; }

        public MessageEntity()
        {
            this.PartitionKey = DateTime.UtcNow.ToString("yyyyMMdd");
            this.RowKey = Guid.NewGuid().ToString();
        }
    }
}

次にリポジトリですが、IMessageRepository インターフェイスに変更はない(IMessage インターフェイスが変更点を内包している)ので、「DAL」フォルダの「MessageRepository.cs」を変更します。Add メソッドでキーワードの格納、Update メソッドと Remove メソッドでキーワードの確認(一致しない場合には前回定義しておいた UpdateException を投げます)、entity2Item メソッドでモデルのキーワードへ string.Empty のセットを行います。

//
        public void Add(IMessage message)
        {
            var entity = new MessageEntity();
            entity.MessageBody = message.MessageBody;
            entity.UserName = message.UserName;
            entity.UpdateKey = message.UpdateKey;

            _context.AddObject(EntitySetName, entity);
            _context.SaveChanges();
        }

        public void Update(IMessage message)
        {
            var dbEntity = getEntity(message.PartitionKey, message.RowKey);

            if (message.UpdateKey != dbEntity.UpdateKey)
            {
                throw new UpdateException("更新用のキーワードが一致しません。");
            }

            dbEntity.MessageBody = message.MessageBody;
            dbEntity.UserName = message.UserName;
            _context.UpdateObject(dbEntity);
            _context.SaveChanges();
        }

        public void Remove(IMessage message)
        {
            var dbEntity = getEntity(message.PartitionKey, message.RowKey);

            if (message.UpdateKey != dbEntity.UpdateKey)
            {
                throw new UpdateException("更新用のキーワードが一致しません。");
            }

            _context.DeleteObject(dbEntity);
            _context.SaveChanges();
        }

        private static Message entity2Item(MessageEntity entity)
        {
            var item = new Message();
            item.MessageBody = entity.MessageBody;
            item.UserName = entity.UserName;
            item.UpdateKey = string.Empty;
            item.PartitionKey = entity.PartitionKey;
            item.RowKey = entity.RowKey;

            return item;
        }

次にコントローラーです。メッセージ修正用のキーワードが一致しないときに、「UpdateException」が発生するので、この例外をハンドリングします。対象となるアクションは Edit, Delete です。

//
        [HttpPost]
        public ActionResult Edit(Message message)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    _repository.Update(message);
                    return RedirectToAction("Index");
                }
                catch (UpdateException ex)
                {
                    ModelState.AddModelError("UpdateKey", ex.Message);
                }
            }

            return View(message);
        }

        [HttpPost]
        public ActionResult Delete(Message message)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    _repository.Update(message);
                    return RedirectToAction("Index");
                }
                catch (UpdateException ex)
                {
                    ModelState.AddModelError("UpdateKey", ex.Message);
                }
            }

            return View(message);
        }

最後にビューです。メッセージ修正用のキーワードについての変更なので、Create, Edit, Delete のビューが修正対象になります。

Create.cshtml

@model MvcWebRole1.Models.IMessage

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>MessageEntity</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.MessageBody)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.MessageBody)
            @Html.ValidationMessageFor(model => model.MessageBody)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.UserName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.UserName)
            @Html.ValidationMessageFor(model => model.UserName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.UpdateKey)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.UpdateKey)
            @Html.ValidationMessageFor(model => model.UpdateKey)
        </div>

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Edit.cshtml

@model MvcWebRole1.Models.IMessage

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>IMessage</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.MessageBody)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.MessageBody)
            @Html.ValidationMessageFor(model => model.MessageBody)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.UserName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.UserName)
            @Html.ValidationMessageFor(model => model.UserName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.UpdateKey)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.UpdateKey)
            @Html.ValidationMessageFor(model => model.UpdateKey)
        </div>

        @Html.HiddenFor(model => model.PartitionKey)
        @Html.HiddenFor(model => model.RowKey)

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Delete.cshtml

@model MvcWebRole1.Models.IMessage

@{
    ViewBag.Title = "Delete";
}

<h2>Delete</h2>

<h3>このデータを削除しますか?</h3>
<fieldset>
    <legend>メッセージ</legend>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.MessageBody)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.MessageBody)
    </div>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.UserName)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.UserName)
    </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.UpdateKey)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.UpdateKey)
            @Html.ValidationMessageFor(model => model.UpdateKey)
        </div>

        @Html.HiddenFor(model => model.PartitionKey)
        @Html.HiddenFor(model => model.RowKey)
</fieldset>
@using (Html.BeginForm()) {
    <p>
        <input type="submit" value="Delete" /> |
        @Html.ActionLink("Back to List", "Index")
    </p>
}

ここまでで「メッセージ更新の際にメッセージ作成者が決めたキーワードを確認する」機能追加が完了です。

このプログラムは、ポイントのみを抜き出していることから、次のような事柄への対応を行なっていません。

  • 一覧画面でのメッセージの列挙順が定まっていない
  • 要求されたキーを持つメッセージがなかったときに例外が発生する

1点目については、Windows Azure テーブルストレージへのクエリでは OrderBy が使用できないため、対応には一工夫が必要です。
2点目については、例外への対応方法が たんたか さんの記事に詳しく書いてあります。


Azure のテーブル ストレージ を ASP.NET MVC で使ってみる(その2)」への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です