WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

SmtpClient でメール送信

by WebSurfer 2010年9月7日 14:25

ユーザー登録の際のパスワードの連絡、ブログにコメントが書き込まれた時の通知などのために、Web アプリケーションにメールの自動送信機能を実装するケースは多いと思います。

自分の HP にも、メールの自動送信の機能を実装しています。もっとも、ユーザー登録するような人はいないし、コメントを書き込む人もいないので、ほとんど役に立っていませんが。(苦笑)

メール送信に利用しているのは .NET Framework のライブラリ SmtpClient クラスです。これを利用すると簡単にメール送信ができます。

ただし、日本語対応に問題があり注意が必要です。以下に、文字コードとしてデフォルトの UTF-8 を使った場合と、日本で広く使われている iso-2022-jp (JIS) を使った場合の注意事項を書いておきます。

(1) UTF-8 を使う場合

UTF-8 の場合は、ほとんど文字化けに悩むことはありませんが、アドレスの表示名に日本語を使う場合のみ注意が必要です。

表示名は MailAddress コンストラクタの第 2 引数で設定しますが、日本語の文字列をそのまま代入すると Q エンコードされてしまいます。

Q エンコードで問題なければいいのですが、受信するメーラによっては B エンコードでないと文字化けするかもしれません。(Outlook Express, Windows メールは問題ありませんでしたが)

B エンコードにするには、以下のサンプルコードにある EncodeMailHeader ようなメソッドを作って日本語の表示名を変換し、それを第 2 引数に代入してください。

件名、本文、添付ファイル名は、以下のサンプルのように日本語をそのまま代入して問題ありません。件名、添付ファイル名は、自動的に UTF-8 の B エンコードに変換されて、ヘッダーに設定されます。本文は、UTF-8 の base64 エンコードとなります。

private string EncodeMailHeader(string str, Encoding enc)
{
  string ret = 
    System.Convert.ToBase64String(enc.GetBytes(str));
  ret = string.Format("=?{0}?B?{1}?=", enc.BodyName, ret);
  return ret;
} 

Encoding enc = Encoding.GetEncoding("utf-8");

MailAddress to = 
  new MailAddress("surfer@mail.abc.co.jp", 
    EncodeMailHeader("日本語の名前", enc));
MailAddress From = 
  new MailAddress("surfer@mail.abc.co.jp", 
    EncodeMailHeader("日本語の名前", enc));

MailMessage mailMessage = new MailMessage(from, to);

mailMessage.Subject = "日本語の件名";
mailMessage.Body = "日本語の本文";

Attachment data = 
  new Attachment("添付ファイル名", 
    System.Net.Mime.MediaTypeNames.Application.Octet);
ContentDisposition disposition = data.ContentDisposition;
disposition.FileName = 
  EncodeMailHeader(System.IO.Path.GetFileName(file), enc);
disposition.CreationDate = 
  System.IO.File.GetCreationTime(file);
disposition.ModificationDate = 
  System.IO.File.GetLastWriteTime(file);
disposition.ReadDate = 
  System.IO.File.GetLastAccessTime(file);

mailMessage.Attachments.Add(data);

SmtpClient smtpClient = new SmtpClient();
smtpClient.Host = "mail.abc.co.jp";
smtpClient.Port = 25;
smtpClient.Send(mailMessage);

(2) iso-2022-jp を使う場合

Encoding を iso-2022-jp にする以外は、メールアドレスを設定するところまでは上記の UTF-8 の場合と同じです。

件名は、直接 Subject プロパティに日本語を代入すると Q エンコードになってしまいます。以下のサンプルのように、EncodeMailHeader メソッドを使って変換してから Subject プロパティに代入しなければなりません。(注:.NET Framework 4.5 で件名のエンコードが変わったそうです。詳しくは下の 2013/5/10 追記を見てください)

一番の問題は本文で、Body を使用すると Content-Transfer-Encoding は 8bit エンコードになってしまいます。これを 7bit にする方法はないそうです。代わりに、AlternateView を使って 7bit エンコードにする方法があります。以下にそのサンプルを書いておきます。

添付ファイル名は、Content-Type の name は、どのよう設定にしても UTF-8 の B エンコードになってしまいます。一方、Content-Disposition の filename は、以下のように設定すれば、iso-2022-jp の B エンコードになります。

以上のように、iso-2022-jp を使うのは、特に本文を AlternateView にするところが、かなり無理やりっぽい感じです。自分的には決してお勧めではないです。

Encoding enc = Encoding.GetEncoding("iso-2022-jp");

private string EncodeMailHeader(string str, Encoding enc)
{
  string ret = 
    System.Convert.ToBase64String(enc.GetBytes(str));
  ret = string.Format("=?{0}?B?{1}?=", enc.BodyName, ret);
  return ret;
}

MailAddress to = 
  new MailAddress("surfer@mail.abc.co.jp", 
    EncodeMailHeader("日本語の名前", enc));
MailAddress From = 
  new MailAddress("surfer@mail.abc.co.jp", 
    EncodeMailHeader("日本語の名前", enc));

MailMessage mailMessage = new MailMessage(from, to);

mailMessage.Subject = EncodeMailHeader("日本語の件名", enc);

// プレーンテキストのAlternateViewを作成
AlternateView htmlView = 
  AlternateView.CreateAlternateViewFromString(
    "日本語の本文",
    enc,
    System.Net.Mime.MediaTypeNames.Text.Plain);

// TransferEncoding.SevenBitを指定
htmlView.TransferEncoding = 
  System.Net.Mime.TransferEncoding.SevenBit;

mailMessage.AlternateViews.Add(htmlView);

Attachment data = 
  new Attachment("添付ファイル名", 
    System.Net.Mime.MediaTypeNames.Application.Octet);
ContentDisposition disposition = data.ContentDisposition;
disposition.FileName = 
  EncodeMailHeader(System.IO.Path.GetFileName(file), enc);
disposition.CreationDate = 
  System.IO.File.GetCreationTime(file);
disposition.ModificationDate = 
  System.IO.File.GetLastWriteTime(file);
disposition.ReadDate = 
  System.IO.File.GetLastAccessTime(file);

mailMessage.Attachments.Add(data);

SmtpClient smtpClient = new SmtpClient();
smtpClient.Host = "mail.abc.co.jp";
smtpClient.Port = 25;
smtpClient.Send(mailMessage);

ということで、結局自分はどうしているかというと、デフォルトの UTF-8 を使っています。UTF-8 が使えないメーラーなんか相手にしない・・・と言いたいところですが、自分宛のメール送信ぐらいにしか使ってないから、何だっていいのです。(笑)

----- 2013/5/10 追記 -----

.NET Framework 4.5 では件名 (Subject) の実装が変わって、上記の「(2) iso-2022-jp を使う場合」に書いたサンプルコードでは Q エンコードになってしまうそうです。その理由など、詳しくは以下のページを参照してください。

.NET Framework 4.5 の System.Net.Mail で日本語の件名を ISO-2022-JP の Base64 でエンコードして送信する方法

二重にエンコードすることにより B エンコードになるそうです。検証していませんが、上記のサンプルコードでは以下のように変更すればよいはずです。

.NET Framework 4 まで(上記サンプルコードの通り)

mailMessage.Subject = EncodeMailHeader("日本語の件名", enc);

.NET Framework 4.5 では二重エンコード

mailMessage.Subject = 
  EncodeMailHeader(EncodeMailHeader("日本語の件名", enc), enc);

Tags: , ,

.NET Framework

xsd ファイルの場所

by WebSurfer 2010年9月6日 13:30

ASP.NET ベースの Web アプリを作成する際、xsd ファイル(型付 DataSet + TableAtadter の定義ファイル)はどこに置いたらいいかという話です。

xsd ファイルの場所

そんなことは知っていると言われそうですね。でも、App_Code フォルダの中に置くとダメなケースがあるということはご存知でしょうか?

(1) Web サイトプロジェクト

App_Code フォルダの中以外に選択肢はありません。上の画像の左側がそれです。

これは型付 DataSet + TableAtadter に限った話ではなく、ユーザー定義のクラスやメソッドをアプリケーションのどこからでも参照できるようにするには、App_Code フォルダに置くしか方法はありません。

(2) Web アプリケーションプロジェクト

少なくとも、VB を開発言語に使っている場合は、App_Code フォルダに置くのは NG です。

App_Code の中に xsd ファイルを作ると、自動生成された Xxx.Designer.vb のメソッド名が衝突を起こし、以下のようなエラーが出ます。

"コンパイル エラー メッセージ: BC30269: 'Public Sub New()' には同じ署名で複数の定義が存在します。"

ただし、上記は VB の時だけで、C# の場合は App_Code の下に xsd ファイルを作っても問題なしでした。

自動生成された Xxx.Designer.vb, Xxx.Designer.cs のコードを比べてみると、以下の違いがありました。

  1. C# の場合は全体が名前空間(アプリケーション名.App_Code)で囲まれているが、VB の場合はそれがない。
  2. C# には VB で名前の衝突を起こした New() というメソッドがない。

アプリケーションルート直下に xsd ファイルを作れば問題なしでした。見た限り自動生成されたコードはどちらも同じなんですが。

何故ルート直下に xsd ファイルを作ると OK で、App_Code フォルダ下では NG なのか理由は調べ切れていませんが、Web アプリケーションプロジェクトでそういう制約があるようです。

というわけで、Web アプリケーションプロジェクトでは、xsd ファイルはアプリケーションルート直下に置くのがよさそうです。上の画像の右側がそれです。

------------- 2011/12/3 追記 -------------

Web アプリケーションプロジェクトで、App_Code にクラスファイルを置くと、Visual Studio と ASP.NET による二重コンパイルの問題があるそうです。なので、特にその必要がない限り、Web アプリケーションプロジェクトで App_Code フォルダを設けるのは避けた方がよさそうです。詳しくは以下のページを参照してください。

App_Code folder doesn't work with Web Application Projects (WAPs)

「特にその必要がない限り」と書きましたが、ASP.NET MVC3 の Razor で @helper メソッドを切り出して利用する場合は、現在のところ App_Code フォルダを使用せざるを得ないようです。詳しくは以下のページを参照してください。

ASP.NET MVC 3 and the @helper syntax within Razor

Tags: , ,

ASP.NET

Access の更新

by WebSurfer 2010年9月4日 20:01

Visual Studio のウィサードを使うと、SQL Server や Access のテーブルを表示して、レコードを INSERT, DELETE, UPDATE するプログラムが簡単に作れます。

ただし、Access でオートナンバー(SQL Server で言うと IDENTITY)を使っている場合、INSERT した時に DB 側で設定したオートナンバー値を DataSet に書き込むところまでは面倒を見てくれません。(SQL Server の場合は面倒見てくれます)

これは、JET データベースエンジン(Microsoft Access およびその他の小規模アプリケーションで使用されている)では、単一バッチで複数のステートメントを実行できないということが理由のようです。

詳しくは、MSDN ライブラリの @@IDENTITY クライシスを管理する の「Microsoft Access/JET の問題」のセクションを参照してください。

ここでは、DataAdapter の RowUpdated イベントを使用して自動生成されたコードとは別のクエリを実行し、オートナンバー値を DataSet に書き込む具体的な例を紹介します。

まず、いつもの手順で型付 DataSet を作り、データソースウィンドウからテーブルを Form にドラッグ&ドロップしてアプリケーションを作ります。以下の画像がその例です。ここまではコードは一行も書く必要がありません。

Visual Studio でのアプリ作成

ただし、ここまでの実装では、新しいレコードを INSERT したとき、オートナンバーとなっている ID の値が DataSet に書き戻されていないので、DataGridView に表示されている ID 値は正しくありません。

そこで、INSERT 直後に発生する DataAdapter の RowUpdated イベントのハンドラで、"SELECT @@IDENTITY" クエリを使って新規 ID 値を取得し、それを DataSet に書き込んでやります。

具体的には、TableAdapter を partial class を使って拡張します。ソリューションにクラスファイルを追加して、以下のようなコードを実装します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.OleDb;

namespace AccessWithAutoNumberAndDataSet.DataSet1TableAdapters 
{
  public partial class XXXTableAdapter
  {
    public void SetHandler()
    {
      this.Adapter.RowUpdated += 
        new OleDbRowUpdatedEventHandler(Adapter_RowUpdated);
    }

    private OleDbCommand cmd = 
      new OleDbCommand("SELECT @@IDENTITY", null);

    private void Adapter_RowUpdated(Object sender, OleDbRowUpdatedEventArgs e)
    {
      cmd.Connection = e.Command.Connection;
      cmd.Transaction = e.Command.Transaction;

      if (e.StatementType == StatementType.Insert && 
        e.Status == UpdateStatus.Continue)
      {
        object obj = cmd.ExecuteScalar();

        if (obj != null && obj.GetType() != typeof(DBNull))
        {
          e.Row["ID"] = (int)obj;
          e.Row.AcceptChanges();
        }
      }
    }
  }
}

これだけでは、ハンドラがイベントにアタッチされていないので、自動生成された Form のコードのコンストラクタに、上記 partial class で定義した SetHandler メソッドを追記します。以下のような感じです。

namespace AccessWithAutoNumberAndDataSet
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();

      // これを追加。
      this.xXXTableAdapter.SetHandler();
    }

これで、新規 ID 値が DataSet に書き込まれ、DataGridView に表示されるようになります。

Tags: , ,

ADO.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年5月  >>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar