ラベル C# の投稿を表示しています。 すべての投稿を表示
ラベル C# の投稿を表示しています。 すべての投稿を表示

2023年1月6日金曜日

C#でFTP接続するならFluentFTPを使おう

 

どうも。ひっくです。

C#でFTP接続する機能を実装する場合、検索するとFtpWebRequestを使用する例が良く出てきます。

しかし.NET 6 以降では、FtpWebRequestは使用が非推奨となり、FTPはサードパーティライブラリの使用が勧められているようです。

今回は、サードパーティライブラリで使い勝手が良さそうな「FluentFTP」について紹介します。

FluentFTPについて


NuGetでリリースされるFTP 、 FTPS ライブラリです。

MITライセンスのため、商用利用も可能です。

広範な FTP コマンドに対応しており、以下のようなことができます。

  • ファイルのアップロード/ダウンロード
  • SSL/TLS 接続
  • ディレクトリ リストの自動解析
  • ファイルのハッシュ/チェックサム
  • ファイルのアクセス許可/CHMOD etc.

 

詳細は公式のREADME.mdを参照してください。

 

FluentFTPを使用する


  1. NuGetでFluentFTPをインストールします。
  2.  

  3. using ディレクティブを追加します。
  4. using FluentFTP;
    

FluentFTPの使用例


  1. FTPサーバーへの接続確認、接続権限有無の確認

    // FTPクライアントを作成して、FTP接続。
    using var conn = new FtpClient("192.168.131.222", "ftpUser", "ftpPass");
    conn.AutoConnect();
    
    // FTPサーバーへの接続結果を確認
    var isSuccess = conn.IsConnected;
    
    // 指定ユーザー、パスワードでの接続結果を確認
    isSuccess = conn.IsAuthenticated;
    
  2. ファイルアップロード、ファイル移動

    using var conn = new FtpClient("192.168.131.222", "ftpUser", "ftpPass");
    conn.AutoConnect();
    
    // ファイルアップロード
    conn.UploadFile(@"C:\UploadFile.pdf", "/docs/UploadFile.pdf");
    
    // ファイル移動
    conn.MoveFile("/docs/UploadFile.pdf", "/docs/sub/UploadFile.pdf");
    
  3. ファイル存在確認、ファイル削除

    using var conn = new FtpClient("192.168.131.222", "ftpUser", "ftpPass");
    conn.AutoConnect();
    
    // ファイル存在確認
    if (conn.FileExists("/docs/UploadFile.pdf"))
    {
        // ファイル削除
        conn.DeleteFile("/docs/UploadFile.pdf");
    }
    

 

上記は1例となります。

他の使用例については、公式のサンプルを参照してみてください。

 

まとめ


FTP操作をしたい場合、FluentFTPは使い勝手が非常に良いと思います。

C#でFTP接続する機能を実装する場合、使用してみてはいかがでしょうか。

 

今回はこのへんで。ではまた!

2022年9月30日金曜日

ディープコピー(詳細コピー)で複製時に気を付けるべき点(C#)

どうも。ひっくです。

クラスのオブジェクトをディープコピー(詳細コピー)したい場合があると思います。

「ディープコピー C#」で検索すると上位に出てくる検索結果上位に、

「BinaryFormatter」クラスを使用した実装例が非常に多く見受けられます。

 

コード量もすっきりしていて一見良さそうに見えますが、

今後は可能な限り使用しない方が良いでしょう、というお話を今回はしようと思います。

BinaryFormatterを利用したディープコピー実装例


まず、コードを見てみましょう。

public static class Utils
{
    /// <summary>
    /// ディープコピー作成
    /// </summary>
    /// <typeparam name="T">型パラメータ</typeparam>
    /// <param name="src">コピー元情報</param>
    /// <returns>コピーオブジェクト</returns>
    public static T DeepClone<T>(this T src)
    {
        using (var memoryStream = new System.IO.MemoryStream())
        {
            var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            binaryFormatter.Serialize(memoryStream, src);
            memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
            return (T)binaryFormatter.Deserialize(memoryStream);
        }
    }
}

[Serializable]
public class Customer
{
    public string CustomerID { get; set; }
    public string CustomerName { get; set; }
    public DateTime LastModified { get; set; }
    public List<PurchaseGoods> PurchaseGoodsList { get; set; }
}

public class PurchaseGoods
{
    public string Name { get; set; }

    public int Price { get; set; }
}

private void CopyTest()
{
    var goods1 = new List<PurchaseGoods>()
    {
        new PurchaseGoods() { Name = "milk", Price = 160 },
        new PurchaseGoods() { Name = "egg", Price = 250 },
    };

    var goods2 = new List<PurchaseGoods>()
    {
        new PurchaseGoods() { Name = "meat", Price = 350 },
        new PurchaseGoods() { Name = "fish", Price = 380 },
    };

    var customers = new List<Customer>()
    {
        new Customer() { CustomerID = "D0001", CustomerName = "Customer1", PurchaseGoodsList = goods1 },
        new Customer() { CustomerID = "D0002", CustomerName = "Customer2", PurchaseGoodsList = goods2 },
    };

    var cloneCustomers = customers.DeepClone();

    customers.First().CustomerID = "A0001";
    customers.First().LastModified = DateTime.Now;
    customers.First().PurchaseGoodsList.First().Name = "tomato";

    foreach (var customer in customers)
    {
        Debug.WriteLine($"CustomerID:{customer.CustomerID} CustomerName:{customer.CustomerName}");
        foreach (var goods in customer.PurchaseGoodsList)
        {
            Debug.WriteLine($"GoodsName:{goods.Name} GoodsPrice:{goods.Price}");
        }
    }
    foreach (var customer in cloneCustomers)
    {
        Debug.WriteLine($"CustomerID:{customer.CustomerID} CustomerName:{customer.CustomerName}");
        foreach (var goods in customer.PurchaseGoodsList)
        {
            Debug.WriteLine($"GoodsName:{goods.Name} GoodsPrice:{goods.Price}");
        }
    }
}

CopyTest() の実行結果は以下のようになります。

「customers」をディープコピーした「cloneCustomers」には、

customersに対する変更が反映されていないことが確認できます。(ディープコピーできていますね)

CustomerID:A0001 CustomerName:Customer1
GoodsName:tomato GoodsPrice:160
GoodsName:egg GoodsPrice:250
CustomerID:D0002 CustomerName:Customer2
GoodsName:meat GoodsPrice:350
GoodsName:fish GoodsPrice:380
CustomerID:D0001 CustomerName:Customer1
GoodsName:milk GoodsPrice:160
GoodsName:egg GoodsPrice:250
CustomerID:D0002 CustomerName:Customer2
GoodsName:meat GoodsPrice:350
GoodsName:fish GoodsPrice:380

 

BinaryFormatterが非推奨とされる理由


BinaryFormatterですが、現在MSDNでは使用が非推奨とされています。

.NET5では警告が表示されるものの使用は可能な状態だったようなのですが、

2022/11リリース予定の.NET7ではとうとうコンパイルエラーになる(破壊的変更が行われる)ようです。

 

そもそも.NET5で警告扱い(ASP.NETでは既定で実行時エラー)だった理由は、逆シリアル化の脆弱性があるからです。

MSDNでは以下の様に説明されています。

「逆シリアル化の脆弱性は、要求ペイロードが安全でない方法で処理される脅威のカテゴリです。 攻撃者がこのような脆弱性をアプリに対して利用することに成功すると、サービス拒否 (DoS)、情報漏えい、またはターゲット アプリ内でのリモート コード実行が発生する可能性があります。」

 

どんな場面で上記の脆弱性に対する危険が発生しうるのか、文章を読むだけではいまいちぴんと来なかったんですが、

OWASP Top 10 2017 A8(リンク先PDF 15ページ参照)を見ると、良くわかりました。

要は信頼できないデータに対してデシリアライズすると、逆シリアル化の脆弱性に対して対策されていない場合、

リモートコード実行や権限書き換えなどによる情報漏洩が起きる可能性があるよねってことなんですよね。

Webアプリのように外から受信したデータを処理することが多いなら、上記が起こりうる確率も高くなるというので

ASP.NETでは既定で実行時エラーになるようにされたんだろう、と個人的に納得しました。

 

.NET7で破壊的変更が行われる以上いずれ置き換えもしなくてはならなくなるし、今後は使用しない方が良いのだと思います。

 

代替手段は?


ICloneable.Cloneを実装して、シャローコピー(簡易コピー)とディープコピー(詳細コピー)の

両方に対応するコピー関数を用意するしか手はなさそうです。

コード量は増えるし、クラスのメンバが増えたり階層化したりするとその都度メンテしないといけないしで、

BinaryFormatterですっきり書きたくなっちゃいますが、可能な限りこっちで実装する方が良いと思われます。

public class Customer : ICloneable
{
    public string CustomerID { get; set; }
    public string CustomerName { get; set; }
    public DateTime LastModified { get; set; }
    public List<PurchaseGoods> PurchaseGoodsList { get; set; }

    public Customer Clone()
    {
        return (Customer)this.MemberwiseClone();
    }
    
    object ICloneable.Clone()
    {
        return this.Clone();
    }

    public Customer DeepCopy()
    {
        Customer customer = this.Clone();
        customer.PurchaseGoodsList = this.PurchaseGoodsList.Select(goods => goods.DeepCopy()).ToList();
        return customer;
    }
}

public class PurchaseGoods : ICloneable
{
    public string Name { get; set; }
    public int Price { get; set; }
    
    public PurchaseGoods Clone()
    {
        return (PurchaseGoods)this.MemberwiseClone();
    }
    
    object ICloneable.Clone()
    {
        return this.Clone();
    }

    public PurchaseGoods DeepCopy()
    {
        return this.Clone();
    }
}

private void CopyTest()
{
    var goods1 = new List<PurchaseGoods>()
    {
        new PurchaseGoods() { Name = "milk", Price = 160 },
        new PurchaseGoods() { Name = "egg", Price = 250 },
    };

    var goods2 = new List<PurchaseGoods>()
    {
        new PurchaseGoods() { Name = "meat", Price = 350 },
        new PurchaseGoods() { Name = "fish", Price = 380 },
    };

    var customers = new List<Customer>()
    {
        new Customer() { CustomerID = "D0001", CustomerName = "Customer1", PurchaseGoodsList = goods1 },
        new Customer() { CustomerID = "D0002", CustomerName = "Customer2", PurchaseGoodsList = goods2 },
    };

    var cloneCustomers = new List<Customer>();
    cloneCustomers.AddRange(customers.Select(customer => customer.DeepCopy()));

    customers.First().CustomerID = "A0001";
    customers.First().LastModified = DateTime.Now;
    customers.First().PurchaseGoodsList.First().Name = "tomato";

    foreach (var customer in customers)
    {
        Debug.WriteLine($"CustomerID:{customer.CustomerID} CustomerName:{customer.CustomerName}");
        foreach (var goods in customer.PurchaseGoodsList)
        {
            Debug.WriteLine($"GoodsName:{goods.Name} GoodsPrice:{goods.Price}");
        }
    }
    foreach (var customer in cloneCustomers)
    {
        Debug.WriteLine($"CustomerID:{customer.CustomerID} CustomerName:{customer.CustomerName}");
        foreach (var goods in customer.PurchaseGoodsList)
        {
            Debug.WriteLine($"GoodsName:{goods.Name} GoodsPrice:{goods.Price}");
        }
    }
}

 

CopyTest() の実行結果は以下で、BinaryFormatterの実装例と同じ結果になります。

CustomerID:A0001 CustomerName:Customer1
GoodsName:tomato GoodsPrice:160
GoodsName:egg GoodsPrice:250
CustomerID:D0002 CustomerName:Customer2
GoodsName:meat GoodsPrice:350
GoodsName:fish GoodsPrice:380
CustomerID:D0001 CustomerName:Customer1
GoodsName:milk GoodsPrice:160
GoodsName:egg GoodsPrice:250
CustomerID:D0002 CustomerName:Customer2
GoodsName:meat GoodsPrice:350
GoodsName:fish GoodsPrice:380

 

まとめ


以上、ディープコピー(詳細コピー)で複製時に気を付けるべき点について取り上げました。

既にBinaryFormatterでのディープコピーを使用してしまっている場合は、古いソースでは警告も表示されないので

脆弱性への対応が必要なケースかどうか、脆弱性への対応が行われているかを改めて確認した方が良いでしょう。

今後新たにコピー機能を追加する場合は、BinaryFormatterは使用しないことをお勧めします。

 

今回はこのへんで。ではまた!

2022年8月26日金曜日

ExcelDataReaderでCSVを読み込む

どうも。ひっくです。

今回は表題の通り、ExcelDataReaderを使用してCSVデータを読み取る方法について

紹介しようと思います。

→ 関連記事 はこちら  ExcelDataReaderでExcelを高速に読み込む

ExcelDataReaderの使用用途、メリット・デメリット


前回記事 ExcelDataReaderでExcelを高速に読み込む でExcelを読み込む際の使用用途等を記載しています。

CSVの場合もExcelと似た使用用途、メリット・デメリットとなるかと思うので、そちらを参照してください。

 

動作環境と公式GitHubについて


 

実際に使用してみる


今回確認は以下で実施しています。

OS: Windows11

Target Framework: .NetCore3.1

VisualStudio: 2022 Community

 

導入手順は以下の通りです。

  1. NuGetから「ExcelDataReader」「ExcelDataReader.DataSet」をインストールします。

  2. ExcelDataReaderを参照し、読み込み用メソッドを用意しておきます。

    以下サンプルを使用してCSVファイルを読み込むと、DataTable型でCSVのデータを取得することができます。

    Excelの読み込みとあまり変わりないですが、CSVを読み込む際は「CreateCsvReader」を使用するのがポイントです。

    読み込み時のエンコーディング指定やセパレータ指定は「ExcelReaderConfiguration」のプロパティに

    CSV読み込み専用のプロパティが用意されているので、そちらを適宜利用してください。

using ExcelDataReader;
using System;
using System.Data;
using System.IO;
using System.Text;

namespace ExcelDataReaderTest
{
    public class ExcelCsvDataReader
    {
        public DataTable ReadCsvData(string path, bool isUseHeaderRow = false)
        {
            DataTable data = null;

            try
            {
                Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
                using var stream = File.Open(path, FileMode.Open, FileAccess.Read);
                var excelReaderConfig = new ExcelReaderConfiguration();

                // 必要に応じて、各プロパティに対し設定を行う(デフォルトの値で問題ない場合は特に変更しない)
                //excelReaderConfig.AutodetectSeparators = new char[','];
                //excelReaderConfig.FallbackEncoding = Encoding.UTF8;
                //excelReaderConfig.AnalyzeInitialCsvRows = 0;

                using var reader = ExcelReaderFactory.CreateCsvReader(stream, excelReaderConfig);
                var conf = new ExcelDataSetConfiguration
                {
                    ConfigureDataTable = (_) => new ExcelDataTableConfiguration
                    {
                        UseHeaderRow = isUseHeaderRow
                    }
                };

                DataSet ds = reader.AsDataSet(conf);
                data = ds.Tables[0];
            }
            catch (Exception)
            {
                throw;
            }

            return data;
        }
    }
}

 

  • 読み込みCSV(例)

    今回は厚生労働省のオープンデータ(新型コロナウイルス 新規陽性者数の推移(日別))を題材にしてみました。

    変数「ds」をビジュアライザーで開いたのが以下になります。

    呼び出し時に第二引数「isUseHeaderRow」を「true」を設定すると、1行目をヘッダとして読み取ります。

    この時、テーブルの列名には以下のように1行目の項目が設定されます。

     

    1行目をヘッダ扱いしない場合、以下のように1行目の項目もデータとして読み取られます。

まとめ


以上、ExcelDataReaderを使用してCSVを読み込む方法について、まとめました。

CSVの読み込み処理は比較的簡単に実装可能な部類と思いますが、より楽に読み込み処理を用意できるのではないでしょうか。

前回紹介したExcelと合わせて、利用してみてください。

 

今回はこのへんで。ではまた!

2019年7月8日月曜日

JavaのStreamAPIメモ [1]

どうも、もりもりです。

Java8でStreamAPIやラムダ式が使えるようになってからだいぶ経ちましたが
C#やVB.NETばかりでやらしてもらってますので
Streamは知ってても使ったことがないんですね~。
なのでメモ。

C#erならLINQを使えて当たり前。
なのでJavaerはStreamを使えて当たり前なのでしょう。

では下記C#のようなコードをJavaで書いたらどうなるのか?

using System;
using System.Collections.Generic;
using System.Linq;

namespace LINQSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // 偶数を取得
            var list1 = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
            var list2 = list1.Where(x => x % 2 == 0);
            list2.ToList().ForEach(x => Console.WriteLine(x));
        }
    }
}

下記がJavaですね。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.*;

class StreamSample {
    public static void main(String args[]) {
        // 偶数を取得
        List<integer> list1 = new ArrayList<integer>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
        Stream<integer> list2 = list1.stream().filter(x -> x % 2 == 0);
        list2.forEach(x -> System.out.println(x));
    }
}


出力結果
2 4 6 8 10

ふむふむ。
stream()をカマさないとダメなわけですね。
少し面倒かも。。。

他にもいろいろと試したかったのですが
VS Codeのバージョンアップで1.36.0となり
(長い間更新してなかった。。。)
設定がガラっと変わってしまったり
一部の機能が使えなくなってしまったりと
そちらに時間を取られてしまいました。。。

なので次回以降でC#のLINQとの書き方の違いなどをもう少し掘り下げて
メモっていきます。

ちなみに、Javaの使い手をJavaerなんて書きましたが
他の言語の呼び名なんか見てみるとおもしろいですね。

 PHP:PHPer(ペチパー)・・・ペチパーて
 Python:Pythonista(パイソニスタ)、Pythonian(パイソニアン)・・・呼ばれたい
 JavaScript:JSer(ジェイサー)・・・なんかかっこいい
 Go:eGoist(エゴイスト)・・・ヤバw
 AWK:awker(オーカー)・・・The Walking Dead観たい気分になるね
 Scala:Scalalian(スカラリアン)、Scalman(スカルマン)・・・ホラーマンみたいな感じ?
 Visual Basic:VBer(ヴィーバー)・・・歯が出てそう


他にもいろいろあるようですが、こんな感じらしいですわ。



2019年2月22日金曜日

サイズの大きいデータをPOSTすると404エラーが発生する

ASP.NETで作成したWeb APIに対し、HttpClientを使用して、JSONデータをPOSTするコードを書きました。

using (var client = new HttpClient())
{
    client.DefaultRequestHeaders.Clear();
    client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

    var content = new StringContent(jsonString, Encoding.UTF8, "application/json");
    var task= client.PostAsync(uri, content).ContinueWith(x =>
    {
        var response = x.Result;
        if (!response.IsSuccessStatusCode)
        {
            throw new Exception(response.ReasonPhrase);
        }

        ・・・
    });
    try
    {
        task.Wait();
    }
    catch (AggregateException ex)
    {
        throw ex.Flatten();
    }
    catch
    {
        throw;
    }
}

40MB近くあるデータをPOSTしたところ、レスポンスに「Not Found」、いわゆる404エラーが返ってきました。












送信データサイズが小さい場合は問題はなく、エラーの原因がサイズの大きさにあることは明らかなのですが、
サーバーが受け取るリクエストの最大サイズはmaxRequestLengthですでに指定しています。
調べてみると、どうやらそれだけでは不十分で、リクエスト内のコンテンツ最大長(maxAllowedContentLength)の指定も
必要だということが分かりました。
web.configにmaxAllowedContentLengthを追加し、送信データサイズ以上の値を指定すると、エラーが返ることはなくなりました。

  <system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="52428800"></requestLimits>
      </requestFiltering>
    </security>
  </system.webServer>

またいつか、どこかで。

2018年11月9日金曜日

C#(.NET Framework)で作ったコンソールアプリを .NET Core を使ってLinuxで動かしてみる!【後編】


こんにちは。よっしーです。

今回は、Visual Studio 2017 の C#(.NET Framework)で作ったコンソールアプリを
.NET Core を使ってLinux上で動かす。という前回からの続きです。

まず、Linux(Ubuntu)へ .NET Core をインストールします。
(現在最新バージョンの .NET Core 2.1 を入れます)

公式サイト(https://dotnet.github.io/)にディストリビューションごとの
インストール手順が記載されていますので参考にして下さい。

私の手元には Ubuntu14.04 があったので、今回はそこへインストールしました。

Microsoftのキーとフィードを登録

$ wget -q https://packages.microsoft.com/config/ubuntu/14.04/packages-microsoft-prod.deb
$ sudo dpkg -i packages-microsoft-prod.deb

.NET Core をインストール
※ 公式サイトの手順は、sdkをインストールしていますが、
今回は実行環境のみインストールしたいので、runtimeをインストールしています。

$ sudo apt-get install apt-transport-https
$ sudo apt-get update
$ sudo apt-get install dotnet-runtime-2.1

これで実行環境が用意できました。

あとは、前回 Visual Studio で作成した、
実行ファイル一式を、Linux上へ配置します。

$ tree
.
|-- dotnet_sample.deps.json
|-- dotnet_sample.dll
|-- dotnet_sample.pdb
`-- dotnet_sample.runtimeconfig.json

これで準備完了です。さっそく実行してみましょう。
dotnet コマンドにDLLファイルをパラメータとして渡して実行します。

$ dotnet dotnet_sample.dll コマンドライン引数A コマンドライン引数B
コマンドライン引数A, コマンドライン引数B
/home/yossy/dotnet_sample

コマンドライン引数とカレントディレクトリのパスを表示し、
text.txt を出力しています。内容を覗いてみると。。。

$ cat test.txt
コマンドライン引数A
コマンドライン引数B

コマンドライン引数として渡した文字列が正しく格納されています。

Windowsで確認した動作と同じ結果となりましたね。
Linux上で.NET Framework を用いたプログラムが動作していることがわかります。




今回、簡単なサンプルプログラムを作ってみましたが、注意する点として、
WindowsとLinuxではパスの扱いが異なる。ということが挙げられます。

例えばディレクトリ階層を "¥¥" で連結しているようなプログラムは、
Windows側だと正しく動きますが、Linux側ではパス異常となります。

× Windows側でしか正しいパスにならない。
string path = Directory.GetCurrentDirectory() + "\\" + "test.txt";

 パス結合するライブラリを用いることでWindows/Linux共通で動作する
string path = Path.Combine(Directory.GetCurrentDirectory(),"test.txt")



OSに依存するような所はライブラリが吸収してくれるはずですので、
上手くそれらを使うようなプログラミングを心がけましょう。

ではまた~。

2018年11月3日土曜日

C#(.NET Framework)で作ったコンソールアプリを .NET Core を使ってLinuxで動かしてみる!【前編】


こんにちは。よっしーです。


さて今回は、タイトルにもある通り、

C#(.NET Framework)で作ったコンソールアプリをLinux上で動かす。

ということをご紹介したいと思います。


.NET Framework って Microsoft が提供してるから、

.NET Framework = Windows というイメージがありますが、

最近ではLinux上でも動作することが出来るんです。


さっそく Visual Studio 2017 Community でチャレンジしてみましょう。


プロジェクトの作成画面から以下を選択します。
 → Visual C#
  → .NET Core
   → コンソール アプリ(.NET Core)


実行すると 「Hello World!」 と出力されるテンプレートが作成されます。


今回は、.NET Framework が Linux上で動作するかを確かめたいので、
Systemのクラスライブラリを使って簡単なサンプルを組んでみました。

<内容>
・カレントディレクトリパスを取得
・コマンドライン引数とカレントディレクトリ名をコンソール出力
・カレントディレクトリにtest.txtを作成し、コマンドライン引数の内容をそのファイルへ出力


あとは、適当に Visual Studio 上から実行する際のコマンドライン引数と、
作業するディレクトリ(カレントディレクトリ)を情報として設定します。


Visual Studio上で実行してみましょう。もちろんWindows上で動作します。

動作結果として、
設定したコマンドライン引数とカレントディレクトリパスがターミナル上に出力され、
出力された test.txt にはコマンドライン引数の内容が出力されます。

まぁ、実装通りですね。。。


下準備はここまでです。

で、このプログラムを Linux 上で動作させるのが今回の目的となりますので、

次に進めていきましょう。


ビルドメニューから、○○○○(プロジェクト名) の発行 というのがあるので選択します。


次の画面に進むので、続けて 発行 を選択します。


すると、対象のディレクトリに以下のような生成物が出力されます。


DLLファイルが出力されていることがわかりますが、これが Linux上 で動作する
プログラムファイルとなります。

DLLなのに実行ファイル?と、Windows脳だとちょっとシックリこないのですが、
Linux で動作させる場合は、dotnet という実行ファイルに このDLLファイルを
指定して動作させるので、そんなに違和感は感じなくなります。

次は Linux 側での操作となりますが、ちょっと説明も長くなってきたので、
今回はここまでとさせてもらいます。

ではまた~。

2018年9月28日金曜日

EntityFrameworkでSQLiteを使ってみた!

Entity FrameworkもSQLiteもそれぞれ使ったことはあるのですが、今回初めてEntity FrameworkからSQLiteを使ったので
その方法をメモ。

まず、System.Data.SQLite.EF6.Migrationsパッケージをインストールします。
この時、もれなく

・EntityFramework
・System.Data.SQLite.Core
・System.Data.SQLite.EF6
・System.Data.SQLite.Linq
・System.Data.SQLite

もインストールされます。

DbContextクラス、エンティティクラスを作成します。

public class SampleDbContext : DbContext
{
    public DbSet<user> Users { get; set; }

    public SampleDbContext() : base("name = Sample")
    {
    }
}

public class User
{
    public int Id { get; set; }

    public string Name { get; set; }
}


App.configに接続文字列を追加します。


<connectionStrings>
  <add name="Sample" connectionString="Data Source=C:\xxxxx\sample.db;" providerName="System.Data.SQLite.EF6" />
</connectionStrings>

enable-migrations/add-migrationを実行すると、エラーが発生しました。

> enable-migrations
No Entity Framework provider found for the ADO.NET provider with invariant name 'System.Data.SQLite'. Make sure the provider is registered in the 'entityFramework' section of the application config file. 

> add-migration InitialCreate
No Entity Framework provider found for the ADO.NET provider with invariant name 'System.Data.SQLite'. Make sure the provider is registered in the 'entityFramework' section of the application config file. 


エラー内容に従って、App.configに設定を追加します。


<providers>
  <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
  <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
  <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
</providers>

再び、add-migrationを実行します。

> add-migration InitialCreate
System.ObjectDisposedException: 破棄されたオブジェクトにアクセスできません。
オブジェクト名 'SQLiteConnection' です。
   場所 System.Data.SQLite.SQLiteConnection.CheckDisposed()
   場所 System.Data.SQLite.SQLiteConnection.get_State()
   場所 System.Data.Entity.Internal.RepositoryBase.CreateConnection()
   場所 System.Data.Entity.Migrations.History.HistoryRepository.QueryExists(String contextKey)
   場所 System.Data.Entity.Migrations.History.HistoryRepository.Exists(String contextKey)
   場所 System.Data.Entity.Migrations.History.HistoryRepository.GetPendingMigrations(IEnumerable`1 localMigrations)
   場所 System.Data.Entity.Migrations.DbMigrator.GetPendingMigrations()
   場所 System.Data.Entity.Migrations.DbMigrator.Scaffold(String migrationName, String namespace, Boolean ignoreChanges)
   場所 System.Data.Entity.Migrations.Design.MigrationScaffolder.Scaffold(String migrationName, Boolean ignoreChanges)
   場所 System.Data.Entity.Migrations.Design.ToolingFacade.ScaffoldRunner.Scaffold(MigrationScaffolder scaffolder)
   場所 System.Data.Entity.Migrations.Design.ToolingFacade.ScaffoldRunner.RunCore()
   場所 System.Data.Entity.Migrations.Design.ToolingFacade.BaseRunner.Run()
破棄されたオブジェクトにアクセスできません。
オブジェクト名 'SQLiteConnection' です。


いろいろ調べてみましたが、原因がよく分かりません。
(SQLServer用接続文字列の場合、エラーは発生しません。)
あきらめて、DbContextクラスのデフォルトコンストラクタで、接続文字列を指定することにしました。

public SampleDbContext() : base(new SQLiteConnection($"Data Source = {Properties.Settings.Default.DatabaseFilePath};"),
false)
{
}


三度、add-migrationを実行します。

> add-migration InitialCreate
No MigrationSqlGenerator found for provider 'System.Data.SQLite'. Use the SetSqlGenerator method in the target migrations configuration class to register additional SQL generators.


Configurationクラスのコンストラクタにメソッド呼び出しを追加します。

public Configuration()
{
    AutomaticMigrationsEnabled = false;
    SetSqlGenerator("System.Data.SQLite", new SQLiteMigrationSqlGenerator());
}


add-migration/update-databaseを実行するとデータベースファイルが作成されました。
余談ですが、データベースファイルパスにフルパスではなくファイル名のみ指定すると、
下記のようなエラーが発生するのでご注意を。

> update-database
System.Data.Entity.Core.ProviderIncompatibleException: CreateDatabase is not supported by the provider.
   場所 System.Data.Entity.Core.Common.DbProviderServices.DbCreateDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection)
   場所 System.Data.Entity.Core.Common.DbProviderServices.CreateDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection)
   場所 System.Data.Entity.Core.Objects.ObjectContext.CreateDatabase()
   場所 System.Data.Entity.Migrations.Utilities.DatabaseCreator.Create(DbConnection connection)
   場所 System.Data.Entity.Migrations.DbMigrator.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
   場所 System.Data.Entity.Migrations.Infrastructure.MigratorBase.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
   場所 System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
   場所 System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration)
   場所 System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.RunCore()
   場所 System.Data.Entity.Migrations.Design.ToolingFacade.BaseRunner.Run()
CreateDatabase is not supported by the provider.


またいつか、どこかで。

2017年12月15日金曜日

共通のカスタムルールを複数プロジェクトに設定する(StyleCopAnalyzers)

どうも。ひっくです。

現在私が携わっている案件では、StyleCopAnalyzersを使用しています。
(StyleCopAnalyzersとは、ソースコードに対する静的分析ツールです。)

今回はソリューション内の複数プロジェクトで共通のカスタムルールを利用する方法について
備忘録も兼ねてまとめようと思います。

StyleCopAnalyzersをインストール(VisualStudio 2015)


まずはインストール。 インストールはNuGetを利用します。

VisualStudioを起動し、「ツール」メニュー⇒「NuGetパッケージマネージャー」⇒「パッケージマネージャーコンソール」の順に選択します。

検索欄に「StyleCop」を入力し、検索実行。

「StyleCop.Analyzers」を選択すると、右のペインに現在ソリューションに含まれるプロジェクトが表示されます。
全てにチェックを入れ、「インストール」を押下します。

これでインストールできました。
なお、アンインストールしたい場合もここからできます。

カスタムルールファイルの作成


次にカスタムルールファイルを作成します。

「ファイル」メニュー⇒「新規作成」⇒「ファイル」の順に選択します。

コード分析規則セットを選択し、「開く」を押下。

StyleCop.Analyzersのドロップダウンリストを開き、適用したいルールを選択します。
選択できたら、ファイルを任意の場所に保存します。


作成したファイルを複数プロジェクトで利用する


ソリューションから、任意プロジェクト(画像ではTestApp)のプロパティを開きます。
(プロジェクト名の上で右クリックすることでプロパティが開きます。)

次に、コード分析を選択し、「この規則セットを実行」⇒「参照」を選択します。
 ※必要に応じて「ビルドに対するコード分析の有効化」にチェックを入れます。

先ほど作成した、カスタムルールファイルを選択し「開く」を押下後、保存します。


これでまず、TestAppで利用するルールセットファイルに「CommonRule.ruleset」が設定されました。


次に、「分析」メニュー⇒「ソリューションのコード分析を構成する」を選択します。

全ての構成を選択します。

プラットフォームが複数ある場合、全てのプラットフォームでカスタムルールを適応したければ
全てのプラットフォームを選択します。

最後に、全てのプロジェクトに対してTestAppで利用することにしたルールを選択します。

これで設定は終了です。

注意すること



1.ルールの再設定が必要な場合がある
  上記で設定したルール設定は、新しくプロジェクトを追加した場合、自動で適応されません。
  手動で設定しなおす必要があります。

  また、プラットフォームについても新しく追加した場合、
  追加したプラットフォームについてはカスタムルール設定が自動で適応されません。
  こちらも手動で設定しなおしましょう。

2.ルール設定を適応するならできる限り早い段階で
  案件がかなり進んだ頃に導入すると、大量の警告が発生します。
  同じルールについては自動で修正することが可能なので、かなり大変という訳ではないですが
  それなりに時間がかかります。
  適応するのであれば、製造フェーズのなるべく早い段階で設定した方が良いと思います。

それでは今回はこのへんで。
ではまた!

2017年5月9日火曜日

ASP.NET Core WebアプリをBluemixにデプロイしてみた!

「Bluemix」って知ってますか?

アプリケーションを開発・実行する、IBMのクラウド・プラットフォームで、Java、Node.js、PHP、Python、Rubyなど
さまざまな言語で開発することができます。

今回は、Visual Studio 2017で作成したASP.NET Coreアプリを、Bluemixにデプロイしてみます。

ASP.NET Core Webアプリの作成


Visual Studio 2017でASP.NET Core Webアプリを作成します。

まず、「ASP.NET Core Web アプリケーション(.NET Core)」を選択し、プロジェクトを作成します。


























「Web API」を選択します。


これでプロジェクトが作成されました。
Getメソッドはデフォルトで文字列のコレクションを返すように実装されています。
デバッグ開始するとJSONデータのダウンロードとなってしまうのが煩わしいため、下記のように変更しました。
 
[HttpGet]
public string Get()
{
    return "value";
}


Bluemixへデプロイ


Bluemix上にデプロイします。


> cf push <アプリ名>


すると、アップロード完了後、下記のようなログが延々と表示されます。
どうやらアプリケーションの起動に失敗しているようです。


Uploading complete
Destroying container
Successfully destroyed container

0 of 1 instances running, 1 starting
0 of 1 instances running, 1 starting
0 of 1 instances running, 1 starting
0 of 1 instances running, 1 starting
(ずっと続く)


ログを確認してみます。


> cf logs <アプリ名> --recent


ヘルスチェックに失敗しているようです。


2017-05-09T16:23:50.19+0900 [APP/0] OUT Now listening on: http://localhost:5000
2017-05-09T16:23:50.19+0900 [APP/0] OUT Application started. Press Ctrl+C to shut down.
2017-05-09T16:24:50.70+0900 [CELL/0] ERR Timed out after 1m0s: health check never passed.


仕方がないので、ヘルスチェックを無効にしてデプロイしてみます。


> cf push <アプリ名> -u none


デプロイに成功し、アプリケーションは正常に開始しましたが、アプリケーションのURLにアクセスするとエラーが発生しました。

 
502 Bad Gateway: Registered endpoint failed to handle the request.



Bluemixのスタートガイドに載っているGitHub上のサンプルアプリと見比べ、いろいろ試してみたところ、Mainメソッド内のホスト生成方法を変更すればよいことが分かりました。

まず、「Microsoft.Extensions.Configuration.CommandLine」をインストールします。






















そして、Mainメソッドを下記のように変更します。
これでデプロイすればOKです。
  
public static void Main(string[] args)
{
    var config = new ConfigurationBuilder()
        .AddCommandLine(args)
        .Build();
 
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseConfiguration(config)
        .UseStartup<Startup>()
        .Build();
 
    host.Run();
}
 

(Bluemixへのデプロイとは直接関係ありませんが、この変更を行うとVisual Studio上でデバッグを開始して起動しなくなってしまいました。プロジェクト設定の「起動URL」に設定されている「api/values」を、絶対URLに変更すると起動するようになります。)






















またいつか、どこかで。

2017年4月20日木曜日

KOBE TodayのAPIを使ってみた!

「KOBE Today」って知ってますか?

今月、神戸市が開設した、神戸市内で開催されるイベント情報を発信するサイトです。
https://data.city.kobe.lg.jp/eventdata/

そのイベント情報を取得できるAPIが公開されているということなので、さっそく使ってみました。

データの形式はLinked Open Data (LOD)、それをSPARQLというSQLに似たクエリ言語を使って取得する、とのこと。
どちらも初めてお目にかかるので、何がなんやらという感じだったのですが、データ構造の理解に下記の情報が役立ちました。

SPARQLを利用した逆マッシュアップ-プログラミングを必要としないアプリ作成方法-

例えば、イベント名、開始・終了日、場所、URLを、終了日順に先頭から10件取得する場合は次のようなクエリを書きます。

 
PREFIX ic: <http://imi.go.jp/ns/core/rdf#>
PREFIX schema: <http://schema.org/>

select distinct ?name ?datef ?datet ?place ?url where {
?s ic:名称/ic:表記 ?name .
?s ic:期間 [ ic:開始日 ?datef; ic:終了日 ?datet ] .
?s ic:開催場所/ic:名称/ic:表記 ?place .
?s schema:url ?url .
}
ORDER BY ?datet
LIMIT 10


このクエリを改行も気にせずそのままURLエンコードして、

 
https://data.city.kobe.lg.jp/sparql?query=(エンコードしたクエリ)


のようにリクエストすればデータが取得できます。
取得したデータをアプリで表示してみました。

















私のような古い人間はとりあえずフォームアプリを作ってしまいがちですが、
ありさんがJavaScriptを使って、このページの右側に表示するようにしてくれました。

またいつか、どこかで。

2017年3月3日金曜日

MCP [70-483] 合格

先日、MCP [70-483] Programming in C# に合格しました。

というのも今年度の下期に社内で資格キャンペーンが開催され
みんな何かしらの資格を受けることになりました。

で、資格は情報処理しか受けたことなかったんですが
たまには違う資格でもと思い受けてみたんですねぇ。えぇ。

で、C#歴は4、5年なんでMCPでも取ろうかと。

[Microsoft Learningサイト]
https://www.microsoft.com/ja-jp/learning/exam-70-483.aspx

ただ勉強方法には非常に困りましたね。
なんせ何が出題されるかよくわからない。。。

出題範囲は下記となっております。

・プログラム フローの管理 (25 - 30%)
・型の作成と使用 (25 - 30%)
・アプリケーションのデバッグとセキュリティの実装 (25 - 30%)
・データ アクセスの実装 (25 - 30%)

自分のスキルを信じ、勉強は前日の徹夜のみ。。。

試験が終わった瞬間の感触は落ちたかなと。。。

「終了」ボタンをポチっとな。

なんと、875点!!!

「デバッグとセキュリティ」以外は9割以上取れてました。
「デバッグとセキュリティ」は6割でした。

試験の内容、気になりますよね~
こればっかりは詳細を書くことはできませんが
C#歴が長い人は、Microsoft Learningサイトに書かれている範囲で
自信のない部分だけに絞って調べてみれば問題ないかと。

「Jump Start」のビデオは日本語字幕対応しているので
お時間あるなら見てみてもよいのではないでしょうか。
私は一通り見ましたがあまり役には立たないかもですね。
C#初めての方は見ておいてもよいと思います。

選択問題は残りニ択で迷う問題が何個かあったのですが
経験からの予測でなんとか乗り切れた感じですね。
問題をよく読まないとひっかかりそうな問題が多い印象でした。

まぁ全く参考になる情報ではありませんね。

ではでは。

2016年12月22日木曜日

Visual Studio 2015でWindowsサービスのインストーラーを作る(ENCORE)

インストーラー作成で発生したエラーについてメモ。

Build Error -1014


ビルドすると

-1014: Cannot rename directory (パス) to (パス).

というエラーが発生しました。
インストーラー出力フォルダをエクスプローラーで開いていたことが原因でした。

エラー 2715


作成したインストーラーを実行したらこんなエラーが表示されました。
しかも、お客様のところへ持参した納品物で発生したもんだから焦りました・・・












原因は分からずじまいですが、プログラムもセットアップもリビルドすれば解消しました。
きちんと動作確認しないといけないですね。

またいつか、どこかで。

おすすめアプリ 暇つぶしに最適!無料で遊べるひっぱりシューティング - HIPPARI Bang Bee

2016年12月16日金曜日

Visual Studio 2015でWindowsサービスのインストーラーを作る(後編)

今回はインストーラーの作成です。

「セットアップと配置」で「InstallShield Limited Edition Project」を選択して、セットアッププロジェクトを追加します。



























なかなか個性的なUIです。

























「Application Files」で指定するインストール先、インストールファイルを中心に説明します。






















64ビットプログラムインストール先の指定


デフォルトではProgram Files (x86)にインストールされます。
Program Filesにインストールする場合は、「ProgramFiles64Folder」を追加します。





















追加されました。
























ProgramFilesFolder下の項目をドラッグして、ProgramFiles64Folderに移動します。






















プロジェクト出力ファイルの指定


「Add Project Outputs」を押して、選択画面を表示します。
日本語リソースを作成するなど多言語化している場合、「ローカライズされたリソース」を選択すればそのリソースもインストールされます。
































プロジェクト外ファイルの指定


「Add Files」を押して、ファイルを追加します。
ただし、セットアッププロジェクトファイル(*.isl)には絶対パスが格納されます。
「<ISProjectFolder>」がセットアッププロジェクトフォルダーを指すので、

&lt;ISProjectFolder&gt;..\SampleLibrary.dll

のように、指定しておいた方がよいでしょうね。

またいつか、どこかで。

おすすめアプリ 暇つぶしに最適!無料で遊べるひっぱりシューティング - HIPPARI Bang Bee

2016年12月9日金曜日

Visual Studio 2015でWindowsサービスのインストーラーを作る(中編)

今回はインストールするWindowsサービスを作成します。

Windowsサービスプロジェクトを作成します。


サービスのデザイナ上で右クリックし、「インストーラーの追加」を選択します。





























インストーラークラスが追加されます。








ServiceInstallerのプロパティを設定します。
ServiceName = サービス名、DisplayName = 表示名、Description = 説明なので、
例えば以下のように設定すると、

インストール後のサービスのプロパティは、以下のように表示されます。



































ServiceProcessInstallerのプロパティは、AccountをLocalSystemに変更します。
























ServiceのプロパティでServiceNameについては、MSDNに下記のように記述されているので、
ServiceInstallerのServiceNameと合わせましょう。

サービスは、ServiceName によってサービス コントロール マネージャーで識別されます。
このプロパティの値は、対応するインストーラー クラスの ServiceInstaller.ServiceName
プロパティのサービスに対して記録された名前と同じ名前にする必要があります。


次回はインストーラーの作成です。

またいつか、どこかで。

おすすめアプリ 暇つぶしに最適!無料で遊べるひっぱりシューティング - HIPPARI Bang Bee