【PC】C# WebPost お便利クラス
筆者はクラウドには懐疑的である(笑)
どう懐疑的なのかは「クラウド・コンピューターの陰」にて明記した。
懐疑的ではあるけれど世のスマホ・タブレットの普及率はどんどんあがり、OS(基本ソフト)=MicroSoft1社独占のような状態ではなくなってしまったので、アプリケーション・ソフトの供給を生業とする弊社もクラウドのシステム開発が急増していたりする。
しかし懐疑的なので(笑)オールクラウドではなく、アプリとのハイブリッドが多く、Web に対して Postしてデータを受け取ったり、処理をしたりということがものすごく多い。
WebClient などを使えばカンタン・・・なのだけども、複数のファイルを送信することもあり、結局は「multipart/form-data」で Postしなくてはならず、毎回毎回 HttpWebRequestとHttpWebResponse を書いて Stream で送って・・・としていると非常にわずらわしい(笑)
そこで、Postするクラスを作ってみた。
複数ファイルを送ったりいろいろできるけれども、完全というわけではなく筆者が困っていない程度の仕上がりだが、とりあえずソース。
public class WebPost :IDisposable { public String RequestUrl { get; set; } public int TimeOut { get; set; } public Boolean Jp { get; set; } private NameValueCollection _param = new NameValueCollection(); public const String CONTENTTYPE_OCTET = "application/octet-stream"; public const String CONTENTTYPE_ZIP = "application/zip"; private class _File { public String name { get; set; } public String path { get; set; } public String contenttype { get; set; } } private List<_File> _files = new List<_File>(); public String Response { get; set; } public String ErrMessage { get; set; } public String UserAgent { get; set; } // // constructor // public WebPost() : this( "",300000,true ) { } public WebPost( String requesturl ) : this( requesturl, 300000,true ) { } public WebPost( int timeout ) : this( "",timeout, true ) { } public WebPost( String requesturl,int timeout ) : this( requesturl, timeout, true ) { } public WebPost( String _requesturl,int timeout,Boolean jp ) { this.RequestUrl = _requesturl; this.TimeOut = timeout; this.Jp = jp; this.Response = ""; this.ErrMessage = ""; this.UserAgent = "NJC"; } public void Param( String name, String value ) { this._param.Add( name, value ); } public void ParamClear( String name, String value ) { this._param = new NameValueCollection(); } public void File( String name, String path ) { File( name, path, CONTENTTYPE_ZIP ); } public void File( String name, String path,String contenttype ) { _File f = new _File(); f.name = name; f.path = path; f.contenttype = contenttype; this._files.Add( f ); } public void FileClear( String name, String value ) { this._files = new List<_File>(); } public Boolean Post() { return Post( null ); } public Boolean Post( ProgressBar pb ) { Boolean ret = false; try { String boundary = System.Environment.TickCount.ToString(); Encoding enc = Encoding.UTF8; HttpWebRequest hwreq = (HttpWebRequest)WebRequest.Create( this.RequestUrl ); hwreq.Method = "POST"; hwreq.UserAgent = this.UserAgent; hwreq.ReadWriteTimeout = this.TimeOut; hwreq.Timeout = this.TimeOut; hwreq.CachePolicy = new RequestCachePolicy( RequestCacheLevel.NoCacheNoStore ); if( Jp ) { var nvc2 = new NameValueCollection(); nvc2.Add( "Accepts-Language", "jp" ); hwreq.Headers.Add( nvc2 ); } hwreq.KeepAlive = false; hwreq.ContentType = "multipart/form-data; boundary=" + boundary; String post = ""; foreach( var p in this._param.AllKeys ) { foreach( var v in this._param.GetValues( p ) ){ post += "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + p + "\"\r\n\r\n" + v + "\r\n"; } } byte[] startData = enc.GetBytes( post ); List<byte[]> headers = new List<byte[]>(); long filescontentlength = 0; byte[] crlf = enc.GetBytes( "\r\n" ); foreach( var f in this._files ) { post = "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + f.name + "\"; " + "filename=\"" + f.path + "\"\r\n" + "Content-Type: " + f.contenttype + "\r\n" + "Content-Transfer-Encoding: binary\r\n\r\n"; byte[] filespostdata = enc.GetBytes( post ); headers.Add( filespostdata ); filescontentlength += filespostdata.Length + new FileInfo(f.path).Length + crlf.Length; } post = "--" + boundary + "--\r\n"; byte[] endData = enc.GetBytes(post); if( pb != null ){ try { pb.Minimum = 0; pb.Maximum = 100; pb.Value = 0; pb.Visible = true; } catch( Exception ) { } } long sssd = 0; long sssc = startData.Length + filescontentlength + endData.Length; hwreq.ContentLength = sssc; using( Stream reqs = hwreq.GetRequestStream() ) { reqs.Write( startData, 0, startData.Length ); sssd += startData.Length; if( pb != null ){ try { pb.Value = (int)( (double)sssd / (double)sssc * 100D ); pb.Refresh(); } catch( Exception ) { } } int i = 0; foreach( var f in this._files ) { reqs.Write( headers[i], 0, headers[i].Length ); using( FileStream fs = new FileStream( f.path, FileMode.Open, FileAccess.Read ) ) { byte[] buffer = new byte[4096]; int readSize = 0; while( true ) { readSize = fs.Read( buffer, 0, buffer.Length ); if( readSize == 0 ) break; sssd += readSize; reqs.Write( buffer, 0, readSize ); if( fs.Length > 0 ) { try { pb.Value = (int)( (double)sssd / (double)sssc * 100D ); pb.Refresh(); } catch( Exception ) { } } } reqs.Write( crlf, 0, crlf.Length ); } i++; } reqs.Write( endData, 0, endData.Length ); sssd += endData.Length; if( pb != null ){ try { pb.Value = (int)( (double)sssd / (double)sssc * 100D ); pb.Refresh(); } catch( Exception ) { } } // // get response // HttpWebResponse hwres = (HttpWebResponse)hwreq.GetResponse(); using( Stream resStream = hwres.GetResponseStream() ) { using( StreamReader sr = new StreamReader( resStream, enc ) ) { this.Response = sr.ReadToEnd(); ret = true; sr.Close(); } resStream.Close(); } hwres.Close(); reqs.Close(); } hwreq.Abort(); } catch( Exception e ) { this.ErrMessage = e.Message; } return ret; } // // destructor // public void Dispose() { } }
何か解放しなくちゃいけないような気がして IDisposable を 継承してたりするけど、よくわからなくて(笑)そのままに
なっている。
使い方はカンタン。
using( WebPost webpost = new WebPost() ) { webpost.RequestUrl = "http://xxx.yyyy.zz"; webpost.TimeOut = 30000; webpost.Param( "value1", "1" ); webpost.Param( "value2", "aaa" ); webpost.File( "File1","C:\\User\\NJC\\DeskTop\\Test.csv" ); webpost.File( "File2","C:\\User\\NJC\\DeskTop\\Test2.csv" ); if( webpost.Post() ) { messageBox.Show( webPost.Response ); }else{ messageBox.Show( webPost.ErrMessage ); } }
WebPostのインスタンスをつくって、PostするUrl を RequestUrlプロパティに。タイムアウトを TimeOutプロパティに。
Postしたいデータを .Param( %1,%2 )で追加、ファイルの場合は .File( name,path )で追加。
準備ができたら Post()メソッドで送信する。
何かレスポンスがあれば Post()メソッドは true を返す。つまり送信成功の場合だ。
false の場合は 送信時に何らかのエラーが発生した場合で、例えば タイムアウトが発生したとか、URL先がなかったとか。
詳細は ErrMessage プロパティにセットされるが、タイムアウトか サーバーと通信できなかったくらいしか詳細はわからずほとんどのエラーが「内部サーバーエラーです」と、あっち(サーバー:POST先)の問題なので、あまり使えない。
実際に筆者が使っている「お便利クラス」は、送信データを暗号化し、あちらのサーバーで復元させるような仕組みも放り込んで https には敵わないが、まあそういうことだ(笑)
アップロード中というプログレスバーを表示したくて、わけのわからないソースだが、適宜解析して使ってもらって構わない。
ただし、ただし、念押し念押し。
自由に使ってもらって構わないが、質問があっても一切お答えしない。責任ももちろん取らない。
くどいようだが、全て自己責任でお願いする。