vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【.NET Framework】Back to ASP.NET Framework 1

はじめに

これまでの通り、普段プライベートのプログラミングでは .NET Core を使っているわけですが、今日は .NET Framework に挑戦しますよ。

理由はまぁ、お察しください

Environments

Base Project

Empty テンプレートでプロジェクトを作ります。
すると名前に恥じない Empty っぷりで、そのまま実行するとエラーになります(localhost:59522)。 f:id:mslGt:20200902223547j:plain

ルーティング

まずルーティングですが、 「RouteConfig.cs」で定義します。
とその前に、「ASP.NET MVC」を NuGet でインストールしておきます。

ASP.NET Core と同じく、「規則ベースのルーティング」と「属性ベースのルーティング」を使うことができます。
(個人的な)扱いやすさから「属性ベースのルーティング」にします。

RouteConfig.cs

using System.Web.Mvc;
using System.Web.Routing;

namespace NetFrameworkSample
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            // 属性ベースのルーティングを有効にする
            routes.MapMvcAttributeRoutes();
        }
    }
}

RouteConfig を使う

そのままだと有効にならないので、 Global Application Class(asax) を作って RouteConfig を追加します。
(Global Application Class(asax) は Visual Studio から追加できます)

Global.asax

<%@ Application Codebehind="Global.asax.cs" Inherits="NetFrameworkSample.Global" Language="C#" %>

Global.asax.cs

using System;
using System.Web.Mvc;
using System.Web.Routing;

namespace NetFrameworkSample
{
    public class Global : System.Web.HttpApplication
    {

        protected void Application_Start(object sender, EventArgs e)
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
    }
}

「Global.asax.cs」には何やらたくさんメソッドが作成されてますが、今回は「Application_Start」だけ使います。

Controller クラスを追加する

属性ベースのルーティングも有効になったところで Controller を追加します。

ProductsController.cs

using System.Web.Mvc;

namespace NetFrameworkSample.Controllers
{
    public class ProductsController: Controller
    {
        public ProductsController()
        {
        }
        [Route("")]
        public string GetMessage()
        {
            return "Hello world!";
        }
    }
}

「/」始まり

「Route」でのルート指定ですが、「[Route("/")]」のようにスラッシュから始めると例外が発生します。
ASP.NET Core だと普通に動くためちょっと混乱しました。

ディレクトリの名前

もう一つルートを追加します。

ProductsController.cs

...
        [Route("Products")]
        public string GetProductsMessage()
        {
            return "Hello Products";
        }
    }
}

localhost:59522/Products」を開くともちろん「Hello Products」が表示されるわけです。

で、「Products」という名前でディレクトリを追加し、「IProductService.cs」という interface を追加してみます。
その後もう一度「localhost:59522/Products」を開こうとすると例外(403)が発生。

どうもルートと同じ名前のディレクトリがある場合、 Controller よりそちらを先に探しに行くらしく、当然 View は見つからない、という状況のようです。

ということでディレクトリ名には注意が必要です。
(「localhost:59522/Api/Products」のようなルートにして回避する、という方法もあります。)

回避する方法もありそうですが。。。

ログ出力 (NLog)

NLog を使ってログ出力しますよ。

Install

  • NLog.Web ver.4.9.3

設定

ASP.NET Framework で NLog を使うには、 Web.config に設定を追加します。

Web.config

...
  <system.webServer> 
    <modules runAllManagedModulesForAllRequests="true"> 
      <add name="NLog" type="NLog.Web.NLogHttpModule, NLog.Web" />
    </modules>
  </system.webServer>
</configuration>

そして .NET Core と同じく nlog.config を追加します。

nlog.config

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true">

    <targets>
        <target xsi:type="Console" name="outputconsole"
            layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />

        <target xsi:type="File" name="outputfile" fileName="C:\tmp\logs\NetFrameworkSample\${date:format=yyyy}\${date:format=MM}\${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
    </targets>

    <rules>
        <logger name="*" minlevel="Debug" writeTo="outputconsole" />>
        <logger name="Microsoft.*" maxLevel="Info" final="true" />
        <logger name="*" minlevel="Debug" writeTo="outputfile" />
    </rules>
</nlog>

インスタンスを作る

「LogManager.GetCurrentClassLogger()」を使ってインスタンスを作ります。

ProductsController.cs

using System.Web.Mvc;
using NLog;

namespace NetFrameworkSample.Controllers
{
    public class ProductsController: Controller
    {
        private readonly Logger _logger;
        public ProductsController()
        {
            _logger = LogManager.GetCurrentClassLogger();
        }
        [Route("")]
        public string GetMessage()
        {
            _logger.Debug("Hello");
            return "Hello world!";
        }
...

これで下記のようなログが出力されます。

2020-09-02 21:05:58.0307||DEBUG|NetFrameworkSample.Controllers.ProductsController|Hello |url: http://localhost/|action: 

DI (Dependency Injection)

ASP.NET Framework は DI をデフォルトで持っていません。
ということで今回 Unity Container を使ってみます。

Microsoft.Extensions.DependencyInjection」も使えるようなので、こちらも試してみたいと思っています。

Install

  • Unity.Container ver.5.11.8
  • Unity.Mvc ver.5.11.1

依存されるクラスを登録

IProductsService.cs

namespace NetFrameworkSample.Products
{
    public interface IProductsService
    {
    }
}

ProductsService.cs

namespace NetFrameworkSample.Products
{
    public class ProductsService: IProductsService
    {
    }
}

UnityConfig.cs

using System;
using Unity;
using NetFrameworkSample.Products;

namespace NetFrameworkSample
{
    /// <summary>
    /// Specifies the Unity configuration for the main container.
    /// </summary>
    public static class UnityConfig
    {
        #region Unity Container
        private static Lazy<IUnityContainer> container =
          new Lazy<IUnityContainer>(() =>
          {
              var container = new UnityContainer();
              RegisterTypes(container);
              
              container.RegisterType<IProductsService, ProductsService>(TypeLifetime.Scoped);
              
              return container;
          });
...

依存クラスを挿入する

Microsoft.Extensions.DependencyInjection」と同様、 Unity Container もコンストラクタインジェクションで依存性を解決します。

ProductsController.cs

using System.Web.Mvc;
using NetFrameworkSample.Products;

namespace NetFrameworkSample.Controllers
{
    public class ProductsController: Controller
    {
        private readonly IProductsService _product;
        public ProductsController(IProductsService product)
        {
            _product = product;
        }
...

インスタンスを登録する

DbContext のように、特定のインスタンスを渡したい場合は「RegisterInstance」を使います。

var sample = new ProductsService();
container.RegisterInstance<IProductsService>(sample, InstanceLifetime.PerContainer);              

NLog のインスタンスも渡すことができるのですが、使うべきではないようです。
というのも、ログに出力した時の「logger」のクラス名が全部「UnityConfig」になってしまうので。。。

Type(Instance) lifetime

Unity Container は「Microsoft.Extensions.DependencyInjection」よりきめ細やかに lifetime の設定ができるようです。

この使い分けも次回以降でもう少し追いかけたいところ。

具象クラスのインスタンスを挿入する

実は RegisterType や RegisterInstance で登録しなくても、クラスインスタンスの挿入をすることができます。

ですが、 Lifetime は Transient であるため、 DbContext(Scoped) のように Transient 以外に設定する必要のあるクラスはやっぱり明示的に登録が必要となります。

おわりに

状況的に仕方がないとはいえ、 .NET Framework でのやり方が非常にググりづらくてツラいですね。。。