うさ☆うさ日記 このページをアンテナに追加 RSSフィード

2006 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 05 | 06 |
2013 | 05 | 08 | 09 | 10 | 11 | 12 |
2014 | 08 |
2015 | 04 | 06 | 08 | 09 | 11 | 12 |
2016 | 01 | 02 | 04 | 05 | 09 | 11 | 12 |
2017 | 04 | 05 | 06 | 10 | 11 |
2018 | 03 | 04 |

2018-04-30

[]Smart.IO.ByteMapper 1.0.0

前にチョコっと作っていたレガシージャパニイッズエンタープライッズ向け固定長ファイルマッパー、色々見直して1.0.0にしました( ˙ω˙)

https://github.com/usausa/Smart-Net-ByteMapper

概要

サンプルデータを使ったベンチでは、bytes to obj、obj to byteの双方共に、100万件/sくらいの処理はできる性能です( ˙ω˙)

使用例

こげな感じ( ˙ω˙)

public class Data
{
    public string StringValue { get; set; }
    public int IntValue { get; set; }
    public decimal DecimalValue { get; set; }
    public bool BoolValue { get; set; }
    public DateTime? DateTimeValue { get; set; }
}
// Configration by expression
var mapperFactory = new MapperFactoryConfig()
    .UseOptionsDefault()
    .DefaultEncoding(Encoding.GetEncoding(932))
    .CreateMapByExpression<ComplexData>(45, config => config
        .ForMember(x => x.StringValue, m => m.Text(10))
        .ForMember(x => x.IntValue, m => m.Integer(8))
        .ForMember(x => x.DecimalValue, m => m.Decimal(10, 2))
        .ForMember(x => x.BoolValue, m => m.Boolean())
        .ForMember(x => x.DateTimeValue, m => m.DateTime("yyyyMMddHHmmss"))
    .ToMapperFactory();

// Create type mapper
var mapper = mapperFactory.Create<ComplexData>();
// buffer & data
var buffer = new byte[mapper.Size];
var data = new Data { ... };

// object to byte array
mapper.ToByte(buffer, 0, data);

// byte array to object
mapper.FromByte(buffer, 0, data);

ASP.NET Coreインテグレーション

ASP.NET Core用のOutputFormatter、InputFormatterを用意しており、以下のように使えます。

// Add formatter in Startup
services.AddMvc(options =>
{
    var config = new ByteMapperFormatterConfig { MapperFactory = mapperFactory };
    config.SupportedMediaTypes.Add("text/x-fixedrecord");
    options.OutputFormatters.Add(new ByteMapperOutputFormatter(config));
    options.InputFormatters.Add(new ByteMapperInputFormatter(config));
});

Controllerでのメソッド定義はこんな感じ。

// Controller method
[Produces("text/x-fixedrecord")]
[HttpGet]
public SampleData[] GetArray()
{
    return new SampleData[] { ... };
}

[Produces("text/x-fixedrecord")]
[HttpGet]
public SampleData GetSingle()
{
    return new SampleData { ... };
}

[Produces("text/x-fixedrecord")]
[ByteMapperProfile("short")]
[HttpGet]
public SampleData[] GetProfileArray()
{
    return new SampleData[] { ... };
}

[HttpPost]
public IActionResult PostArray([FromBody] SampleData[] values)
{
    return Ok();
}

使用方法

NuGetで「Usa.Smart.IO.ByteMapper」を参照してください。

カスタムコンバーターを使う場合は「Usa.Smart.IO.ByteMapper.Options」を追加してください。

ASP.NET Coreインテグレーションを使う場合は「Usa.Smart.IO.ByteMapper.AspNetCore」を追加してください。


その他詳細はTestとExampleプロジェクトを参考にしてちょ( ˙ω˙)

そもそもなぜ固定長レコードファイルか?

レガシージャパニイッズエンタープライッズの領域では固定長ファイルが転がっているというのもありますが。

それ以外にも、ハンディーターミナルで10万件の商品データを扱うケースなんかを考えてみます。


例えば、商品コードからの詳細取得について、サーバ側で実装して都度通信する方法も考えられますが、バックヤードでは無線LANが届かない場所で使われるケースなんかも存在します。

では、端末内でDBを使って処理するかといえば、データ件数に対する登録時間や変更頻度(例えば上位のマスタは毎日更新されて、それと同期する必要がある)の問題からそれが適切でないケースもあります。

っということで、主キーによるバイナリサーチしかしないんだから、固定長データファイルをそのままDLしてきてそのまま使うのが最適解では?、っということにもなったりするわけです(・∀・;)


同じモバイル端末でも、華やかなコンシューマーの世界とは違う、泥臭いレガシーエンタープライズな世界( ˙ω˙)

2018-03-05

[]Smart.Navigation 1.0.0

画面遷移用ライブラリ、やっとこさ1.0にしました(・∀・;)

https://github.com/usausa/Smart-Net-Navigation


旧名Smart.Windows.MvcとかSmart.Windows.Navigationの最新版。

概要

使用方法

Exampleプロジェクトを参考にしてちょ(・ω・)

NuGetではWindows Formsなら「Usa.Smart.Navigation.Windows.Forms」を追加、Xamarin.Formsなら「Usa.Smart.Navigation.Forms」を追加。

2017-11-05

[]Android(Xamarin.Forms)でFeliCaのデータを参照するアプリを作る

ふと思い立ち、交通系電子マネーの残額と履歴を参照するアプリを作ってみました(・ω・)

f:id:machi_pon:20171109160249p:image


ソースはこちら。

https://github.com/usausa/Example-Net-Xamarin/tree/master/FeliCaReader


やっていることは、通常のAndroidアプリでNfcFを使ってRead without Encryptionするのと同じです。

変わっているのは、Xamarin.Formsを使うと言うことでIFeliCaService及びIFeliCaReaderという形で抽象化を行い、Android依存のNFCの処理をAndroid非依存な部分とやりとりしているくらいです。

このソースでは実装をAndroid非依存な部分へ渡す必要があるのがIFeliCaServiceだけなので、Android側からはAppクラスへ直接値を渡してしまっていますが、他のサービスも使ったりするようになるのであればDIを使えば良いです。


っで、iOSもCoreNFCでNFCが開発者に開放されたとか言っていますが、所詮NDEFを扱えるとか言うレベルの話のようで( ゚д゚)ペッ

iOSでも、Androidと同じくらい楽チンポにFeliCaが扱えるようになると良いんですけどね〜(´・ω・`)


ちなみに、このソースでは交通系(サイバネ領域、システムコード0x0003)で情報を参照しているだけですが、ネットでググればシステムコード0xFE00(共通領域、WAONEdynanacoとか)についての解析情報も見つかると思うので、それらへの対応も簡単にできると思います。

2017-10-05

[]Xamarin.iOSでLambdaExpression.Compile()が通る件

Xamarin.iOSではコード生成はサポートされないですが、LambdaExpression.Compile()は通りますね(・ω・)?*1

Reflectionベースの実装が使われるとか聞いた気もしますが、とりあえず動作を確認してみます。

試す内容

よくある高速なリフレクションの処理を題材にしてみましょうか。

コンストラクタのFunc<TTarget>*2プロパティアクセスのFunc<TTarget, TMember>及びAction<TTarget, TMember>について、以下の3パターンで処理を生成し、AndroidiOSで動かして結果を見てみることにします。

  • Reflectionによる生成
  • LambdaExpression.Compile()による処理の生成
  • Reflection.EmitによるILベースでの生成

っで、各手法でのコードは以下のようになります。

Reflection
public static Func<TTarget> CreateReflectinFactory<TTarget>(ConstructorInfo ci)
{
    // Supported default constructor only
    // Activator.CreateInstance(Type) fast than ConstructoInfo.Invoke() when zero parameters
    return () => (TTarget)Activator.CreateInstance(ci.DeclaringType);
}
public static Func<TTarget, TMember> CreateReflectionGetter<TTarget, TMember>(PropertyInfo pi)
{
    return target => (TMember)pi.GetValue(target);
}
public static Action<TTarget, TMember> CreateReflectionSetter<TTarget, TMember>(PropertyInfo pi)
{
    return (target, value) => pi.SetValue(target, value);
}
Expression
public static Func<TTarget> CreateExpressionFactory<TTarget>(ConstructorInfo ci)
{
      // Supported default constructor only
      return Expression.Lambda<Func<TTarget>>(Expression.New(ci)).Compile();
}
public static Func<TTarget, TMember> CreateExpressionGetter<TTarget, TMember>(PropertyInfo pi)
{
    var parameterExpression = Expression.Parameter(typeof(TTarget));
    var propertyExpression = Expression.Property(parameterExpression, pi);
    return Expression.Lambda<Func<TTarget, TMember>>(
        propertyExpression,
        parameterExpression).Compile();
}
public static Action<TTarget, TMember> CreateExpressionSetter<TTarget, TMember>(PropertyInfo pi)
{
    var parameterExpression = Expression.Parameter(typeof(TTarget));
    var parameterExpression2 = Expression.Parameter(typeof(TMember));
    var propertyExpression = Expression.Property(parameterExpression, pi);
    return Expression.Lambda<Action<TTarget, TMember>>(
        Expression.Assign(propertyExpression, parameterExpression2),
        parameterExpression,
        parameterExpression2).Compile();
}
Emit
public static Func<TTarget> CreateEmitFactory<TTarget>(ConstructorInfo ci)
{
    // Supported default constructor only
    var dynamic = new DynamicMethod(string.Empty, typeof(TTarget), Type.EmptyTypes, true);
    var il = dynamic.GetILGenerator();

    il.Emit(OpCodes.Newobj, ci);
    il.Emit(OpCodes.Ret);

    return (Func<TTarget>)dynamic.CreateDelegate(typeof(Func<TTarget>));
}
public static Func<TTarget, TMember> CreateEmitGetter<TTarget, TMember>(PropertyInfo pi)
{
    var method = new DynamicMethod(string.Empty, typeof(TMember), new[] { typeof(TTarget) }, true);
    var generator = method.GetILGenerator();
    var getter = pi.GetGetMethod(true);

    generator.DeclareLocal(typeof(object));
    generator.Emit(OpCodes.Ldarg_0);
    EmitTypeConversion(generator, pi.DeclaringType, true);
    EmitCall(generator, getter);
    if (pi.PropertyType.IsValueType)
    {
        generator.Emit(OpCodes.Box, pi.PropertyType);
    }

    generator.Emit(OpCodes.Ret);

    return (Func<TTarget, TMember>)method.CreateDelegate(typeof(Func<TTarget, TMember>), null);
}
public static Action<TTarget, TMember> CreateEmitSetter<TTarget, TMember>(PropertyInfo pi)
{
    var method = new DynamicMethod(string.Empty, typeof(void), new[] { typeof(TTarget), typeof(TMember) }, true);
    var generator = method.GetILGenerator();
    var setter = pi.GetSetMethod(true);

    generator.Emit(OpCodes.Ldarg_0);
    EmitTypeConversion(generator, pi.DeclaringType, true);
    generator.Emit(OpCodes.Ldarg_1);
    EmitTypeConversion(generator, pi.PropertyType, false);
    EmitCall(generator, setter);
    generator.Emit(OpCodes.Ret);

    return (Action<TTarget, TMember>)method.CreateDelegate(typeof(Action<TTarget, TMember>));
}
private static void EmitTypeConversion(ILGenerator generator, Type castType, bool isContainer)
{
    if (castType.IsValueType)
    {
        generator.Emit(isContainer ? OpCodes.Unbox : OpCodes.Unbox_Any, castType);
    }
    else if (castType != typeof(object))
    {
        generator.Emit(OpCodes.Castclass, castType);
    }
}

private static void EmitCall(ILGenerator generator, MethodInfo method)
{
    var opcode = (method.IsStatic || method.DeclaringType.IsValueType) ? OpCodes.Call : OpCodes.Callvirt;
    generator.EmitCall(opcode, method, null);
}
コード生成サポートの判定

ついでに、Emitによる手法iOSでは通らないわけですが、判定用に雑ですが以下のような処理も用意してみます。

public static bool IsCodegenAllowed()
{
    try
    {
        new DynamicMethod(string.Empty, typeof(object), Type.EmptyTypes, true);
        return true;
    }
    catch (Exception e)
    {
        return false;
    }
}

実行結果

っで、計測自体は若干雑ですが、自分のRobbinとiPhone SEで動作確認してみた結果が以下になります。

数値は処理にかかった時間で、小さいほど速いということになります。

f:id:machi_pon:20171109153754p:image

f:id:machi_pon:20171109153830p:image

とりあえずiOSではExpressionを使った方法がReflectionと同等以下になってはいますね。

まとめ

iOSでは素直にReflectionを使え。

っというか、コンパイルタイム自動生成最強(`・ω・´)


最後に、今回の検証で使ったコードを以下に置いておきます。

https://github.com/usausa/Example-Net-Xamarin/tree/master/AotCheck

*1:ただし、以下の確認はiOSではDebugビルドでのみ行っているのでReleaseビルドでは違うかもしれません

*2:手抜きで引数無しのコンストラクタのみのサポートにしています

2017-06-09

[]ASP.NET Coreで設定ファイルによるDIの設定を行い環境によって使用されるコンポーネントを変更する(楽屋裏)

記事はこちら

え〜

これらは用途に応じて組み合わせて使い分けるべきもので、どれかがあれば他はいらないとかそういう種のものですたい(´・ω・`)


「○○という手法はもう古い」「時代は□□(という手法)」みたいな煽り系エンジニアも多かったりしますが、単に特定のユースケースしか考えていないだけだったりすることもあったりで。

人様のおうんこな言説に惑わされることなく、自分の要件にあった手法を自分で選択できる技術者でありたいものです(`・ω・´)

2017-05-08

[]NuGetで参照するだけで自動生成機能が組み込まれるライブラリのNuGetパッケージを作成する

記事はこちら

Xamarin関連で調べた内容のまとめです。

ちゅーか、実は既に結構使われている技だったりするもの(・ω・;)?

なお、ポイントは「NuGetで参照するだけ」で「csprojを手編集で弄ったりすることが不要」っという点です。

2017-04-22

[]Xamarin.Formsを使うライブラリを.NET Standardで作る時のメモ

毎回忘れて調べるので備忘録(・ω・;)

「クラスライブラリ(.NET Standard)」でプロジェクトを作成した後、csprojを開いて下記のように<PackageTargetFallback>を追加。

  <PropertyGroup>
    <TargetFramework>netstandard1.1</TargetFramework>
    <PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
  </PropertyGroup>

これでXamarin.FormsをNuGetから参照可能に。

2016-12-09

[]TestHostを用いたASP.NET Coreアプリケーションのテスト

Microsoft.AspNetCore.TestHostを使用して、ASP.NET Core Webアプリケーションをオンメモリにホストした状態でのテストを行います。

TestHostを用いることで、HTTPリクエストをエミュレートしたエンドツーエンドの試験が可能になります。

要は、Javaで埋め込みコンテナを使ってテストする〜、みたいな奴のASP.NET Core版です。

環境

  • Visual Strudio 2015
  • .NET Core Tooling Preview 2 for Visual Studio 2015

前提

テスト対象となるASP.NET Core Webアプリケーションのプロジェクトが作成され、.NET Core 1.1へアップデート済みの状態とします。

また、今回はわかりやすいようにAPIコントローラーの呼び出しをテスト対象とするため、APIコントローラーを追加済みの状態とします。

サンプル

以下に完成形のサンプルを用意しています。

手順

テストプロジェクトの構築手順について記述します。

ASP.NET Coreアプリケーションのソリューションにテストプロジェクトとして「Class Library (.NET Core)」のプロジェクトを追加します。

作成されたプロジェクトはターゲットフレームワークが.NET Standardになっているため、そのままではASP.NET Coreアプリケーションのプロジェクトを参照できないのでproject.jsonの内容を変更します。

ターゲットフレームワークの変更と、必要なライブラリの参照を追加したproject.jsonは以下の様になります。

{
  "version": "1.0.0-*",

  "testRunner": "xunit",

  "dependencies": {
    "Microsoft.NETCore.App": {
      "type": "platform",
      "version": "1.1.0"
    },
    "dotnet-test-xunit": "2.2.0-preview2-build1029",
    "xunit": "2.2.0-beta4-build3444",
    "Microsoft.AspNetCore.TestHost": "1.1.0",
    "Microsoft.DotNet.InternalAbstractions": "1.0.0",
    "TestHostExample": "1.0.0-*"
  },

  "frameworks": {
    "netcoreapp1.1": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  }
}

行っている作業は以下になります。

  • .NETStandard 1.6の設定から.NETCoreApp 1.1用にdependenciesとframeworksを変更
  • testRunnerセクション追加
  • dotnet-test-xunit、xunitへの参照追加
  • Microsoft.AspNetCore.TestHostへの追加
  • Microsoft.DotNet.InternalAbstractionsへの追加
  • Webアプリケーションプロジェクト(TestHostExample)への参照の追加

なお、Microsoft.DotNet.InternalAbstractionsについては、1.0環境では問題ありませんが、Microsoft.AspNetCore.Mvcを1.1.0にあげると問題が発生する(Visual Studioのテストエクスプローラーにテストが表示されない、dotnet testコマンドによるテストでは例外が発生する)ため追加しています。

テストコード

テストコードは以下の内容となります。

using System.Net.Http;

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;

using Xunit;

public class ValuesControllerTest
{
    [Fact]
    public async void GetReturnValueArray()
    {
        var config = new ConfigurationBuilder().Build();

        var host = new WebHostBuilder()
            .UseConfiguration(config)
            .UseStartup<Startup>();

        var server = new TestServer(host);

        using (var client = server.CreateClient())
        {
            var request = new HttpRequestMessage(HttpMethod.Get, "/api/values/");
            var response = await client.SendAsync(request);

            var content = await response.Content.ReadAsStringAsync();

            Assert.Equal(content, "[\"value1\",\"value2\"]");
        }
    }
}

テスト対象プロジェクトのStartupをホストしてTestServerのインスタンスを作成し、そこからクライアントの作成を行って、HTTPリクエスト/レスポンスのエミュレーションによるテスト内容になります。

テスト用のカスタマイズを行う場合、TestServerのインスタンス作成前にconfigやWebHostBuilderをいじったりします。

あとは、Visual Srudioのテストメニューやdotnet testコマンドによりテストを実行するだけです。


ホスティングまで疑似ってテストすると重いと思うかもしれませんが、テストの実行時間は1個目こそ500msくらいかかりますが、それ以降は10msくらいで終わるので気にしなくていいと思います。


ところで、Startup内でServiceProviderに登録しているコンポーネントについて、テスト用のモックへの置き換えはどこでやるのがええんやろね(・ω・)?

Startupのコンポーネント登録箇所をvirtualにしてそこで、とか?

2016-12-08

[]ASP.NET Core用カスタムResolver拡張作った

この内容だとコントローラー以降のDIにしか対応していないので、[FromServices]や@injectでのDIにも対応できるように完全版を作った(・ω・)

作るにあたって調査した内容は以下にまとめた。

ソース

使い方

ASP.NET Coreプロジェクトを作って、1.1化して、NuGetでUsa.Smart.Resolver.AspNetCoreをインスコ

Startupのソースでusing Smart.Resolverして、ConfigureServices()を以下の様に変更すればOK。

public class Startup
{
    private readonly StandardResolver resolver = new StandardResolver();

...
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddMvc();

        return SmartResolverHelper.BuildServiceProvider(resolver, services);
    }
...
}

概念

  • [FromServices]や@injectで使用されるコンポーネントASP.NET Coreのコンポーネント含め、全てのコンポーネントはIServiceProviderで管理される
  • ConfigureServices()で独自のIServiceProviderを返すと、標準のIServiceProviderに変わってそれが使用される
  • よって、カスタムResolverと連携するIServiceProviderを作ることで、任意のコンポーネントの管理を独自Resolverに任せることができる

特徴

そもそもなんでカスタムResolverを使いたいかと言えば、標準のIServiceProviderでは対応していないコンディショナルバインディングとかをしたいから(´・ω・`)

そういうのが不要ならば、標準のIServiceProviderを使っていればOK(・∀・)b

2016-11-24

[][]CentOS 7.2上にASP.NET Core Webアプリデプロイしてサービス化する

昨日の続きとして、.NET Core 1.1をインストールしたCentOS上に、Visual Studioで作ったASP.NET Core Webアプリデプロイして、ついでにサービス化する方法について(・ω・)

手順

nginxとの連携とかはせず、ASP.NET Core Webアプリを動かす最低限の作業について記述。

また、アプリビルドCentOS上で行うのではなく、Visual Studio上でビルドしたものをCentOSデプロイするパターンについて記述。

プロジェクト作成

Visual Studioで「ASP.NET Core Web Application (.NET Core)」を作成。

1.1へ更新

Visual Studioで作成されるひな形は1.0用の構成。

CentOSには1.1が入っているので1.1用に更新。

まずはNuGetですべてのパッケージを更新。


次に、project.jsonの次の箇所を更新。

  • 変更前
  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  },
  • 変更後
  "frameworks": {
    "netcoreapp1.1": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  },

また、次の箇所も変更。

  • 変更前
    "Microsoft.NETCore.App": "1.1.0",
  • 変更後
    "Microsoft.NETCore.App": {
      "version": "1.1.0",
      "type": "platform"
    },

project.json全体は以下のような内容。

{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.1.0",
      "type": "platform"
    },
    "Microsoft.AspNetCore.Diagnostics": "1.1.0",
    "Microsoft.AspNetCore.Mvc": "1.1.0",
    "Microsoft.AspNetCore.Razor.Tools": {
      "version": "1.0.0-preview2-final",
      "type": "build"
    },
    "Microsoft.AspNetCore.Routing": "1.1.0",
    "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
    "Microsoft.AspNetCore.StaticFiles": "1.1.0",
    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
    "Microsoft.Extensions.Configuration.Json": "1.1.0",
    "Microsoft.Extensions.Logging": "1.1.0",
    "Microsoft.Extensions.Logging.Console": "1.1.0",
    "Microsoft.Extensions.Logging.Debug": "1.1.0",
    "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
    "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.1.0",
  },

  "tools": {
    "BundlerMinifier.Core": "2.2.306",
    "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final",
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
  },

  "frameworks": {
    "netcoreapp1.1": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  },

  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },

  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true
    }
  },

  "publishOptions": {
    "include": [
      "wwwroot",
      "**/*.cshtml",
      "appsettings.json",
      "web.config"
    ]
  },

  "scripts": {
    "prepublish": [ "bower install", "dotnet bundle" ],
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}
URL設定

デフォルトではlocalhostからのアクセスのみ許可されるので、外部からも接続できるようにUseUrls()の記述を追加。

public static void Main(string[] args)
{
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseUrls("http://*:80/")
        .UseStartup<Startup>()
        .Build();

    host.Run();
}

なお、この時点でVisual Studio上でもデバッグ動作できることを確認しておく。

できなければ何かが不足している状態。

デプロイ

今回はLinux上でビルドするのではなく、開発環境上でビルドしたものを配布する。


Visual Studioの[ビルド]メニューからアプリケーションの発行を実施。

発行方法は「ファイルシステム」で、ターゲットの場所はデフォルトだと「.\bin\Release\PublishOutput」。

上記フォルダの内容がCentOS上へコピーするファイル。


なお、発行と同じことをコマンドラインからMSBuildで行う場合、以下のコマンドで指定したディレクトリにファイルを作成可能。

MSBuild src\Xxx\Xxx.xproj /p:Configuration=Release /t:PackagePublish /p:PublishOutputPathNoTrailingSlash=bin\Release\PublishOutput
Firewall設定

CentOSに対して外部から接続可能にするためFirewallを設定。

[root@centos ~]# firewall-cmd --zone=public --add-port=http --permanent
success
[root@centos ~]# firewall-cmd --reload
success
[root@centos ~]#
動作確認

CentOS上のコピー先は「/opt/webapp」として、そこに移動してdotnetコマンドを実行。

[root@centos ~]# cd /opt/webapp
[root@centos webapp]# dotnet WebApplication.dll
Hosting environment: Production
Content root path: /opt/webapp
Now listening on: http://*:80
Application started. Press Ctrl+C to shut down.

この状態で、外部からWebブラウザで動作を確認する。

問題がなければ画面が表示され、コンソールにもログが表示される。

サービス化

ついでに上記アプリケーションのサービス化。


まず、起動用のスクリプトとして以下のような/opt/webapp.shを作成。

#!/bin/bash

cd /opt/webapp
dotnet WebApplication.dll

実行権限の設定。

[root@centos ~]# chmod 755 /opt/webapp.sh

次に、サービスの起動スクリプトとして以下のような/etc/systemd/system/webapp.serviceを作成。

[Unit]
Description = webapp daemon

[Service]
ExecStart = /opt/webapp.sh
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target

以下のコマンドで正しく作成されていることを確認。

[root@centos ~]# systemctl list-unit-files --type=service | grep webapp
webapp.service                              enabled

サービスの開始。

[root@centos ~]# systemctl start webapp

これで、OS再起動してもWebアプリケーションが起動。

なお、これはあくまで最低限の設定なので、実際にプロダクションに導入する際には、もう少しスクリプトの内容を検討すること。


…っということで、これで実行環境がLinuxだからという理由でJavaでWebアプリを作っていたような人達も、.NET Coreで同程度のことはできるようになったわけで、これまで.NETのウィークポイントであった実行環境面が克服されたのでした٩(๑´3`๑)۶

みんな、これからはWebアプリASP.NET Coreで作るのじゃ(`・ω・´)