部分ビューでの検証エラーの表示 の続きです。前回部分ビュー上での入力内容の検証エラー表示ができているので、今回は新規作成画面、詳細表示画面、修正画面、削除確認画面を jQuery UI の機能を使ってダイアログ表示してみます。使用するフレームワークは ASP.NET MVC 4 ですが、MVC 3 でも基本的に同じです(一部追加されたメソッドを使っていますが 😉 )。2011年4月に「ASP.NET MVC と jQuery で新規登録ダイアログと編集ダイアログ」で書いた内容のアップデートです 🙂
ダイアログ表示の基本的な方針として、ダイアログ表示を行う画面は jQuery Ajax API の Load メソッドで取得することとします。
画面は次のとおりです。
最初の一覧画面
追加ダイアログの表示
追加ダイアログでの検証エラーの表示
追加完了画面
詳細ダイアログの表示
Ajax で詳細画面を取得して、ページ内容の動的変更を行なっているので、追加完了メッセージが残っています。消したい場合には Ajax で動的変更を行うのと同時に jQuery で DOM ツリーを操作することで消すことができます(以下同様)。
修正ダイアログの表示
修正ダイアログでの検証エラーの表示
修正完了画面
削除確認ダイアログの表示
削除完了画面
それではプログラムです。内容的に前回の続きになりますが、フレームワークを MVC 3 から MVC 4 に変更しているので、プロジェクトの作成から書いていきます。
まずは、新しいプロジェクトで ASP.NET MVC 4 Web アプリケーションを作成します(プロジェクト名は「TestJqueryUi005」としました)。 プロジェクト テンプレートは「基本」を選択します。
まずはモデルから。Models フォルダに Contact クラスを作ります。
using System.ComponentModel.DataAnnotations;
namespace TestJqueryUi005.Models
{
public class Contact
{
public int Id { get; set; }
[Required]
[Display(Name = "名前")]
public string Name { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "メールアドレス")]
public string Email { get; set; }
[Required]
[DataType(DataType.PhoneNumber)]
[Display(Name = "電話番号")]
public string Phone { get; set; }
[Required]
[Display(Name = "関係")]
public int RelationshipId { get; set; }
public virtual Relationship Relationship { get; set; }
}
}
次に Relationship クラスを作ります。
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace TestJqueryUi005.Models
{
public class Relationship
{
public int Id { get; set; }
[Display(Name = "関係")]
public string Name { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
}
}
次にビューの表示に利用する DataContainer を作ります。
using System.Linq;
namespace TestJqueryUi005.Models
{
public class DataContainer
{
public IQueryable<Contact> Contacts { get; set; }
public Contact TargetContact { get; set; }
/// <summary>
/// ビュー側で jQuery UI のダイアログ表示の振り分けに使用します
/// </summary>
public string Action { get; set; }
}
}
次に DbContext です。プロジェクトに DAL フォルダを作り、作成した DAL フォルダに ContactContext クラスを作ります。
using System.Data.Entity;
using TestJqueryUi005.Models;
namespace TestJqueryUi005.DAL
{
public class ContactsContext : DbContext
{
public ContactsContext() : base("ContactsDb") { }
public DbSet<Contact> Contacts { get; set; }
public DbSet<Relationship> Relationships { get; set; }
}
}
次に DB のイニシャライザです。DAL フォルダに ContactDbInitializer クラスを作ります。
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using TestJqueryUi005.Models;
namespace TestJqueryUi005.DAL
{
public class ContactDbInitializer : DropCreateDatabaseAlways<ContactsContext>
{
protected override void Seed(ContactsContext context)
{
new List<Relationship> {
new Relationship { Name = "友人" },
new Relationship { Name = "同僚" },
new Relationship { Name = "顧客" },
}.ForEach(m => context.Relationships.Add(m));
context.SaveChanges();
new List<Contact> {
new Contact {
Name = "テストユーザー1", Email = "test1@example.com", Phone = "1234-11-1234",
Relationship = context.Relationships.Where(w => w.Name == "友人").First()
},
new Contact {
Name = "テストユーザー2", Email = "test2@example.com", Phone = "1234-22-1234",
Relationship = context.Relationships.Where(w => w.Name == "同僚").First()
},
}.ForEach(m => context.Contacts.Add(m));
context.SaveChanges();
}
}
}
動作テスト用なので、起動毎に DB を初期化させています。
次に、プロジェクト トップの Web.Config に接続文字列を設定します。
<?xml version="1.0" encoding="utf-8"?>
~省略~
<connectionStrings>
<add name="ContactsDb" connectionString="Data Source=|DataDirectory|\ContactsDb.sdf" providerName="System.Data.SqlServerCe.4.0" />
</connectionStrings>
~省略~
DB は SQL Server Compact 4.0 の DB ファイルをプロジェクトの App_Data に作成するように設定しています。
次に DB のイニシャライザの設定です。Global.asax の Application_Start メソッドに設定します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using TestJqueryUi005.DAL;
namespace TestJqueryUi005
{
~省略~
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Database.SetInitializer(new ContactDbInitializer());
}
}
}
以上で DB 関係の設定は終了です。
次にリポジトリです。
DAL フォルダに IContactsRepository インターフェイスを作ります。
using System;
using System.Linq;
using TestJqueryUi005.Models;
namespace TestJqueryUi005.DAL
{
public interface IContactsRepository : IDisposable
{
IQueryable<Contact> FindContacts();
Contact GetContact(int id);
Contact AddContact(Contact contact);
void UpdateContact(Contact contact);
void DeleteContact(int id);
IQueryable<Relationship> FindRelationships();
Relationship GetRelationship(int id);
void Save();
}
}
次に ContactsRepository クラスを作ります。
using System;
using System.Linq;
using TestJqueryUi005.Models;
namespace TestJqueryUi005.DAL
{
public class ContactsRepository : IContactsRepository
{
private ContactsContext _context;
public ContactsRepository()
{
_context = new ContactsContext();
}
#region Contacts
public IQueryable<Models.Contact> FindContacts()
{
return _context.Contacts.AsQueryable();
}
public Models.Contact GetContact(int id)
{
return _context.Contacts.Find(id);
}
public Models.Contact AddContact(Models.Contact contact)
{
return _context.Contacts.Add(contact);
}
public void UpdateContact(Models.Contact contact)
{
_context.Entry(contact).State = System.Data.EntityState.Modified;
}
public void DeleteContact(int id)
{
var contact = _context.Contacts.Find(id);
if (contact == null)
{
return;
}
_context.Contacts.Remove(contact);
}
#endregion Contacts
#region Relationships
public IQueryable<Relationship> FindRelationships()
{
return _context.Relationships.AsQueryable();
}
public Relationship GetRelationship(int id)
{
return _context.Relationships.Find(id);
}
#endregion Relationships
public void Save()
{
_context.SaveChanges();
}
#region IDisposable
private bool _disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
_disposed = true;
if (disposing)
{
// マネージ リソースの解放処理
}
// アンマネージ リソースの解放処理
_context.Dispose();
}
#endregion IDisposable
}
}
次にヘルパーです。プロジェクトに Helpers フォルダを作り、Helpers フォルダに PropertyHelper クラスを作ります。
using System;
using System.Linq.Expressions;
namespace TestJqueryUi005.Helpers
{
public class PropertyHelper
{
public static string GetPropertyName<T>(Expression<Func<T>> e)
{
return ((System.Linq.Expressions.MemberExpression)e.Body).Member.Name;
}
}
}
次にサービス層です。プロジェクトに Services フォルダを作ります。
最初にサービス層からコントローラーへ検証エラーを通知する ValidateException 例外です。Services フォルダに ValidateException クラスを作ります。
using System;
namespace TestJqueryUi005.Services
{
enum ValidationError
{
DuplicateName = 1,
NotFound,
}
[Serializable]
class ValidateException : Exception
{
private string _targetName;
public ValidateException() : base() { }
/// <summary>
/// 検証エラーとなったプロパティ名とエラーコードを例外で通知します。
/// </summary>
/// <param name="targetName"></param>
/// <param name="errorCode"></param>
public ValidateException(string targetName, ValidationError errorCode)
: base()
{
_targetName = targetName;
base.HResult = (int)errorCode;
}
public ValidateException(string message) : base(message) { }
public ValidateException(string message, Exception inner) : base(message, inner) { }
protected ValidateException(System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) { }
/// <summary>
/// 検証エラーのコードを取得します。
/// </summary>
public ValidationError ErrorCode
{
get { return (ValidationError)base.HResult; }
}
/// <summary>
/// 検証エラーのメッセージを取得します。
/// </summary>
public override string Message
{
get
{
if (base.HResult != 0)
{
return getMessage((ValidationError)base.HResult);
}
return base.Message;
}
}
/// <summary>
/// 検証エラーが発生したプロパティ名を取得します。
/// </summary>
public string TargetName
{
get { return _targetName; }
}
private string getMessage(ValidationError errorCode)
{
switch (errorCode)
{
case ValidationError.DuplicateName:
return "同じ名前が既に登録されています!";
case ValidationError.NotFound:
return "対象データが見つかりません。要求は実行出来ませんでした!";
default:
return "不明なエラーが発生しました!";
}
}
}
}
次に ITestService インターフェイスを作ります(名前が単体テストと紛らわしいというツッコミはなしで 😉 )。
using System;
using System.Linq;
using TestJqueryUi005.Models;
namespace TestJqueryUi005.Services
{
public interface ITestService : IDisposable
{
IQueryable<Contact> GetListContacts();
Contact GetContact(int id);
void CreateContact(Contact contact);
void EditContact(Contact contact);
void DeleteContact(int id);
IQueryable<Relationship> GetListRelations();
Relationship GetRelation(int id);
}
}
次に TestService クラスを作ります。
using System;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Linq;
using TestJqueryUi005.DAL;
using TestJqueryUi005.Helpers;
using TestJqueryUi005.Models;
namespace TestJqueryUi005.Services
{
public class TestService : ITestService
{
private IContactsRepository _repository;
public TestService() : this(new ContactsRepository()) { }
public TestService(IContactsRepository repository)
{
_repository = repository;
}
#region Contacts
public IQueryable<Contact> GetListContacts()
{
return _repository.FindContacts();
}
public Models.Contact GetContact(int id)
{
return _repository.GetContact(id);
}
public void CreateContact(Contact contact)
{
// 関係の存在の確認(クライアント側で細工された場合に発生)
checkRelationExistence(contact.RelationshipId,
PropertyHelper.GetPropertyName(() => contact.RelationshipId));
// 名前の重複の確認
if (_repository.FindContacts().Where(w => w.Name == contact.Name).Count() != 0)
{
throw new ValidateException(PropertyHelper.GetPropertyName(() => contact.Name),
ValidationError.DuplicateName);
}
_repository.AddContact(contact);
_repository.Save();
}
public void EditContact(Contact contact)
{
// 関係の存在の確認(クライアント側で細工された場合に発生)
checkRelationExistence(contact.RelationshipId,
PropertyHelper.GetPropertyName(() => contact.RelationshipId));
// 名前の重複の確認
if (_repository.FindContacts().Where(w => w.Name == contact.Name && w.Id != contact.Id).Count() != 0)
{
throw new ValidateException(PropertyHelper.GetPropertyName(() => contact.Name),
ValidationError.DuplicateName);
}
try
{
_repository.UpdateContact(contact);
_repository.Save();
}
catch (DbUpdateConcurrencyException e)
{
if (e.InnerException.GetType() == typeof(OptimisticConcurrencyException))
{ // 同時実行違反例外(この場合はエンティティが削除されている)
throw new ValidateException("", ValidationError.NotFound);
}
throw;
}
}
public void DeleteContact(int id)
{
_repository.DeleteContact(id);
_repository.Save();
}
#endregion Contacts
#region Relations
public IQueryable<Relationship> GetListRelations()
{
return _repository.FindRelationships();
}
public Relationship GetRelation(int id)
{
return _repository.GetRelationship(id);
}
#endregion Relations
#region Validate
private void checkRelationExistence(int id, string propertyName)
{
if (GetRelation(id) == null)
{
throw new ValidateException(propertyName, ValidationError.NotFound);
}
}
#endregion Validate
#region IDisposable
private bool _disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
_disposed = true;
if (disposing)
{
// マネージ リソースの解放処理
}
// アンマネージ リソースの解放処理
_repository.Dispose();
}
#endregion
}
}
あとはコントローラーとビューですが、長くなったので次回に続きます。
「部分ビューでの検証エラーの表示と jQuery UI のダイアログ表示(1)」への1件のフィードバック