none
asp.net core mvc 3.1 でURLの取得方法について RRS feed

  • 質問

  • 以前に作成したasp.net mvc 5.2 で作成した自作のHelper で下記の記載をしています。
      var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);

    上記を asp.net core mvc 3.1 で実現する場合、下記で実装するようなことが記載されています。

    public static IUrlHelper GetUrlHelper(this IHtmlHelper html)
    {
    var urlFactory = html.ViewContext.HttpContext.RequestServices.GetRequiredService<IUrlHelperFactory>();
    var actionAccessor = html.ViewContext.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>();
    return urlFactory.GetUrlHelper(actionAccessor.ActionContext);
    }
    「Startup.cs」に下記を記載
    public void ConfigureServices(IServiceCollection services)
    {
    services.AddControllersWithViews();
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    }
    <参考元>
    https://stackoverflow.com/questions/45502998/how-to-create-a-mvc5-mvchtmlstring-in-asp-net-core
    https://cloud6.net/so/c%23/859417


    上記のように対応すると、下記のエラーがでます。

    InvalidOperationException: No service for type 'Microsoft.AspNetCore.Mvc.Infrastructure.IActionContextAccessor' has been registered.

    解決方法を教えて頂けると幸いです。
    目的は自作のHelperで、URLが取得できれば良いので他の対応方法でも良いのですか。

    以上、よろしくお願いいたします。
    2021年4月11日 19:19

回答

  • sun_sun_sunさん、こんにちは。フォーラムオペレーターのKumoです。
    MSDNフォーラムにご投稿くださいましてありがとうございます。

    >>InvalidOperationException: No service for type 'Microsoft.AspNetCore.Mvc.Infrastructure.IActionContextAccessor' has been registered.
    このエラーについては、「StartUp」で「IActionContextAccessor」を直接挿入できます:
    services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

    また、ナビゲーションバーに基づいて対応するURLを生成したいのでしょうか。
    そうであれば、タグヘルパーをカスタムすればいいかと思います。

    次のリンクが参考になるかもしれません。
    Author Tag Helpers in ASP.NET Core


    どうぞよろしくお願いいたします。 

    MSDN/ TechNet Community Support Kumo ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、 ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~

    • 回答としてマーク sun_sun_sun 2021年4月24日 17:40
    2021年4月16日 1:54
    モデレータ

すべての返信

  • > 以前に作成したasp.net mvc 5.2 で作成した自作のHelper で下記の記載をしています。
    > var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);

    それの使い方の具体例を書いてください。その情報があるのとないのとでは回答が違ってくるかも。

    目的が果たせれば手段(自作のHelper)は問わないということであれば、目的を書いてください。View で要求 URL が取得できれば良い?
    2021年4月12日 0:22
  • SurferOnWww様、いつも回答、ありがとうございます。

    目的は、タブを実装することです。
    今回、質問に関係する箇所は、★の箇所です。

    ■■生成するHTML(CSSは省略)
    <div id="container">
    <div id="tabbar">
    <ul class="clearfix">
    <li><a href="/ControllerA/tabA" onclick="setLinkNo();">tabA_name</a></li> ★
    <li class="selected"><a href="/ControllerA/tabB" onclick="setLinkNo();">tabB_name</a></li> ★
    <li><a href="/ControllerA/tabC" onclick="SetLinkNo();">tabC_name</a></li> ★
    </ul>
    </div>
    </div>
    setLinkNo()は、jQuery等で、リンクのパラメータにキーを渡します。

    ■■asp.net mvc 5.2 でにの下記を実装していました。

    ■cshtml(_Layoutのファイル)タブが増えたときは、ここ(●のところ)に追加します。

    <div id="container">
    <div id="menubar">
    @Html.CaseTabNav(new[] {
    new CaseTabNavItem{Text="tabA_name", Action="tabA", Controller = "ControllerA", GetSelected = ((a, c) => c == "ControllerA" && a == "tabA")}, ●
    new CaseTabNavItem{Text="tabB_name", Action="tabB", Controller = "ControllerA", GetSelected = ((a, c) => c == "ControllerA" && a == "tabB")},
    new CaseTabNavItem{Text="tabC_name", Action="tabC", Controller = "ControllerA", GetSelected = ((a, c) => c == "ControllerA" && a == "tabC")}
    })
    </div>
    </div>

    ■ヘルパーの自作関数
    public static MvcHtmlString CaseTabNav(this HtmlHelper helper, IEnumerable<CaseTabNavItem> navItems)
    {
    (省略)
    var urlHelper = new UrlHelper(helper.ViewContext.RequestContext); ★
    a.Attributes.Add("href", urlHelper.Action(item.Action, item.Controller)); ★
    (省略)
    }

    public class CaseTabNavItem
    {
    public string Text { get; set; }
    public string Action { get; set; }
    public string Controller { get; set; }
    public Func<string, string, bool> GetSelected { get; set; }
    }


    asp.net coreの場合は、Viewで、jQueryで書いたほうが良いのでしょうか。
    ただ、サイドメニューの表示を権限で表示、非表示の制御も、ヘルパー関数で行ってましたが、
    ヘルパー関数のほうが書きやすいような(asp.net coreでは現時点未実装)。
    すみません、違う質問になってしまいました。

    以上、よろしくお願いいたします。

    2021年4月13日 7:41
  • > ■■生成するHTML(CSSは省略)

    パッと見ですが、その html を生成するには部分ビューが良さそうに思えます。部分ビューを使わない事情・理由が何かあるのでしょうか?

    2021年4月13日 9:05
  • SurferOnWww様、回答、ありがとうございます。

    部分ビューは使用しています。
    すみません、全体象が記載していませんでしたので
    改めて記載します。
    ■■1は_Layoutのファイルのファイル
    ■■2に各タブ:actionが対応します。



    ■■1.cshtml(_Layoutのファイル)タブが増えたときは、ここ(●のところ)に追加します。■■

    @RenderSection("commonpart") ■ ←共通画面

    省略

    <div id="container">
    <div id="menubar">
    @Html.CaseTabNav(new[] {
    new CaseTabNavItem{Text="tabA_name", Action="tabA", Controller = "ControllerA", GetSelected = ((a, c) => c == "ControllerA" && a == "tabA")}, ●
    new CaseTabNavItem{Text="tabB_name", Action="tabB", Controller = "ControllerA", GetSelected = ((a, c) => c == "ControllerA" && a == "tabB")},
    new CaseTabNavItem{Text="tabC_name", Action="tabC", Controller = "ControllerA", GetSelected = ((a, c) => c == "ControllerA" && a == "tabC")}
    })
    </div>
    </div>

    省略

    @RenderBody()■←タブで変わる画面



    ■■2.各タブのcshtml■■

    下記を設定しています。上記1.の■の部分がそれぞれ対応します。

    @{
    Layout = "~/Views/Shared/■■1のファイルを指定";
    }

    ■共通画面
    @section commonpart{
        @Html.Partial("[共通画面]", Model)
    }

    <ここにタブが見える>

    ■タブで変わる画面
    例えばあるタブの画面は、tableで。
    <table>
    <tbody>
    (省略)
    </tbody>
    </table>

    以上、よろしくお願いいたします。
    2021年4月13日 11:07
  • 自分が言っているのは HTML ヘルパーに代えて部分ビューを使うというのはいかかでしょうかということなのですが・・・ 考え違いですか?
    2021年4月13日 12:23
  • > 自分が言っているのは HTML ヘルパーに代えて部分ビューを使うというのはいかかでしょうかということなのですが

    理解されてないようですので、どういうことか説明しておきます。

    Visual Studio のテンプレートで生成する ASP.NET Core MVC プロジェクトで、以下の画像の赤枠部分を部分ビューを使って表示した例です。質問者さんのケースではデリゲートを渡すなど特殊なことをしていますので、それが可能かどうかは分かりませんが。



    組み込みタグヘルパーのアンカータグヘルパーと部分タグヘルパーを使います。

    ASP.NET Core の組み込みタグ ヘルパー
    https://docs.microsoft.com/ja-jp/aspnet/core/mvc/views/tag-helpers/built-in/?view=aspnetcore-5.0

    Model

    namespace MvcCoreApp.Models
    {
        public class AnchorTagData
        {
            public string Controller { get; set; }
            public string Action { get; set; }
            public string Text { get; set; }
        }
    }

    部分ビュー _Navi.cshtml

    @using MvcCoreApp.Models
    @model IEnumerable<AnchorTagData>
    
    @foreach (var tagdata in Model)
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-controller=@tagdata.Controller asp-action=@tagdata.Action>@tagdata.Text</a>
        </li>
    }

    _Layout.cshtml

    <!DOCTYPE html>
    <html lang="en">
    <head>
        ・・・中略・・・
    </head>
    <body>
        <header>
            <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
                <div class="container">
                    <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">MvcCoreApp</a>
                    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                            aria-expanded="false" aria-label="Toggle navigation">
                        <span class="navbar-toggler-icon"></span>
                    </button>
                    <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                        <partial name="_LoginPartial" />
                        <ul class="navbar-nav flex-grow-1">
                            <partial name="_Navi.cshtml"
                                     model='new List<AnchorTagData> { 
                                                new AnchorTagData { Controller="Home", Action="Index", Text="Home" },
                                                new AnchorTagData { Controller="Home", Action="Privacy", Text="Privacy" },
                                                new AnchorTagData { Controller="People", Action="Index", Text="People" },
                                                new AnchorTagData { Controller="Messages", Action="Index", Text="Messages" },
                                                new AnchorTagData { Controller="Validation", Action="Create", Text="Validation" },
                                                new AnchorTagData { Controller="Upload", Action="Index", Text="FileUpload" },
                                                new AnchorTagData { Controller="People", Action="Index", Text="People" },
                                                new AnchorTagData { Controller="Products", Action="Index", Text="Products" },
                                                new AnchorTagData { Controller="Ajax", Action="Index", Text="Ajax" },
                                                new AnchorTagData { Controller="IHttpClientFactory", Action="Index", Text="HttpClient" },
                                            } '/>
                      ・・・後略・・・



    • 編集済み SurferOnWww 2021年4月14日 2:14 訂正
    2021年4月14日 1:59
  • カスタム tag ヘルパーを使って、上の私のレスの部分ビューを使った場合とほぼ同じ結果になる方法を書いておきます。

    ASP.NET Core MVC でもカスタム html ヘルパーは使えるようですが Core では推奨されてないような感じで、Mocrosoft のドキュメントにも見当たりませんので。

    カスタム tag ヘルパー作成には以下の記事が参考になると思います。

    ASP.NET Core のタグ ヘルパー作成
    https://docs.microsoft.com/ja-jp/aspnet/core/mvc/views/tag-helpers/authoring?view=aspnetcore-5.0

    問題はカスタム tag ヘルパー内で controller と action method の名前からパスを作成するところです。

    例えば action method に [HttpPost("/fileupload")] というような属性が付与されているような場合、名前から文字列を組み立てて <controller name>/<action method name> というパスを設定すると HTTP 404 Not Found となってしまいます。

    それゆえ、url ヘルパーを使って Url.Action(action: "index", controller: "upload"); というようにしてパスを取得したいというのが今回の質問だと理解しています。

    url ヘルパーを tag ヘルパー内で取得するのは以下の記事が参考になると思います。

    how to resolve ~ root link from a TagHelper
    https://stackoverflow.com/questions/34385547/how-to-resolve-root-link-from-a-taghelper

    実装例を以下に書きます。

    Model

    上のレスと同じです

    namespace MvcCoreApp.Models
    {
        public class AnchorTagData
        {
            public string Controller { get; set; }
            public string Action { get; set; }
            public string Text { get; set; }
        }
    }


    NaviTagHelper クラス(カスタム tag ヘルパー)

    using System.Collections.Generic;
    using Microsoft.AspNetCore.Razor.TagHelpers;
    using MvcCore5App2.Models;
    using Microsoft.AspNetCore.Mvc.Routing;
    using Microsoft.AspNetCore.Mvc.ViewFeatures;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using Microsoft.AspNetCore.Mvc;
    
    namespace MvcCore5App2.TagHelpers
    {
        public class NaviTagHelper : TagHelper
        {
            private readonly IUrlHelperFactory urlHelperFactory;
    
            public NaviTagHelper(IUrlHelperFactory urlHelperFactory)
            {
                this.urlHelperFactory = urlHelperFactory;
            }
    
            [ViewContext]
            [HtmlAttributeNotBound]
            public ViewContext ViewContext { get; set; }
    
            public IEnumerable<AnchorTagData> Info { get; set; }
    
            public override void Process(TagHelperContext context, TagHelperOutput output)
            {
                var urlHelper = urlHelperFactory.GetUrlHelper(ViewContext);
    
                output.TagName = "ul";
                var @class = "navbar-nav flex-grow-1";
                output.Attributes.SetAttribute("class", @class);
                var content = "";
                foreach (var data in Info)
                {
                    var path = urlHelper.Action(
                        action: data.Action,
                        controller: data.Controller);
                    content += $"<li class=\"nav-item\">" +
                        $"<a class=\"nav-link tex-dark\" href=\"{path}\">{data.Text}</a></li>\r\n";
                }
                output.Content.SetHtmlContent(content);
            }
        }
    }

    _Layout.cshtml

    <navi info="model"></navi> というのがカスタム tag ヘルパーです

    @addTagHelper MvcCore5App2.TagHelpers.NaviTagHelper, MvcCore5App2
    
    @{
        IEnumerable<AnchorTagData> model = 
            new List<AnchorTagData> {
                new AnchorTagData { Controller="Home", Action="Index", Text="Home" },
                new AnchorTagData { Controller="Home", Action="Privacy", Text="Privacy" },
                new AnchorTagData { Controller="PkUnique", Action="Index", Text="PkUnique" }};
    }
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        ・・・中略・・・
    </head>
    <body>
        <header>
            <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
                <div class="container">
                    <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">MvcCore5App2</a>
                    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                            aria-expanded="false" aria-label="Toggle navigation">
                        <span class="navbar-toggler-icon"></span>
                    </button>
                    <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                        <navi info="model"></navi>                    
                        <partial name="_LoginPartial" />
                    </div>
                </div>
            </nav>
        </header>
    
        ・・・後略・・・


    上の tag ヘルパーのコードの場合は startup.cs でのサービスの登録 services.AddSingleton<IActionContextAccessor, ActionContextAccessor>(); は必要ないです。

    結果は以下の通りです。






    • 編集済み SurferOnWww 2021年4月15日 14:29 訂正
    2021年4月14日 12:34
  • sun_sun_sunさん、こんにちは。フォーラムオペレーターのKumoです。
    MSDNフォーラムにご投稿くださいましてありがとうございます。

    >>InvalidOperationException: No service for type 'Microsoft.AspNetCore.Mvc.Infrastructure.IActionContextAccessor' has been registered.
    このエラーについては、「StartUp」で「IActionContextAccessor」を直接挿入できます:
    services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

    また、ナビゲーションバーに基づいて対応するURLを生成したいのでしょうか。
    そうであれば、タグヘルパーをカスタムすればいいかと思います。

    次のリンクが参考になるかもしれません。
    Author Tag Helpers in ASP.NET Core


    どうぞよろしくお願いいたします。 

    MSDN/ TechNet Community Support Kumo ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、 ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~

    • 回答としてマーク sun_sun_sun 2021年4月24日 17:40
    2021年4月16日 1:54
    モデレータ
  • html ヘルパーを使った例も書いておきます。

    using System;
    using Microsoft.AspNetCore.Html;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using System.Web;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.AspNetCore.Mvc.Routing;
    using Microsoft.AspNetCore.Mvc.Infrastructure;
    using Microsoft.AspNetCore.Mvc;
    using System.Collections.Generic;
    using MvcCoreApp.Models;
    
    namespace MvcCoreApp.HtmlHelpers
    {
        public static class MvcCoreAppHtmlHelpers
        {
            // 静的クラスなのでコンストラクタ経由での DI はできない。
            // IServiceProvider を取得してそれから IServiceCollection に登録されている
            // IUrlHelperFactory と IActionContextAccessor のインスタンスを取得
            public static IHtmlContent AchorTag(this IHtmlHelper helper,
                                                IEnumerable<AnchorTagData> Info)
            {
                IServiceProvider provider = helper.ViewContext.HttpContext.RequestServices;
    
                var urlFactory = provider.GetRequiredService<IUrlHelperFactory>();
    
                var actionAccessor = provider.GetRequiredService<IActionContextAccessor>();
    
                var urlHelper = urlFactory.GetUrlHelper(actionAccessor.ActionContext);
    
                var content = "";
                foreach (var data in Info)
                {
                    var path = urlHelper.Action(
                        action: data.Action,
                        controller: data.Controller);
                    content += "<li class=\"nav-item\">" +
                        $"<a class=\"nav-link text-dark\" href=\"{path}\">" +
                        $"{HttpUtility.HtmlEncode(data.Text)}</a>" +
                        "</li>\r\n";
                }
    
                var output = $"<ul class=\"navbar-nav flex-grow-1\">{content}</ul>";
    
                return new HtmlString(output);
            }
        }
    }

    html ヘルパーは静的クラスになるので、コンストラクタ経由での IUrlHelperFactory と IActionContextAccessor の DI はできないのですが、上記のようにして IServiceCollection(DI コンテナ)から取得することは可能でした。

    IUrlHelperFactory は、Visual Studio のテンプレートで作る ASP.NET Core 3.1 MVC アプリのプロジェクトにはデフォルトで IServiceCollection に登録されているようです。

    IActionContextAccessor の方は自分でコードを書いてサービスに登録します。具体的には、startup.cs の ConfigureServices メソッドで以下のようにします。

    // 追加
    using Microsoft.AspNetCore.Mvc.Infrastructure;
     
    namespace MvcCoreApp
    {
        public class Startup
        {
     
            // ・・・中略・・・
     
            public void ConfigureServices(IServiceCollection services)
            {
                // 追加
                services.AddSingleton<IActionContextAccessor, 
                                      ActionContextAccessor>();
     
            // ・・・中略・・・
    }


    最初の質問のコードでも同様に IActionContextAccessor をサービスに登録しているようですが、なぜエラーになったかはわかりません。


    • 編集済み SurferOnWww 2021年4月16日 9:08 変更
    2021年4月16日 6:52
  • Kumo さん>

    > このエラーについては、「StartUp」で「IActionContextAccessor」を直接挿入できます:
    > services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

    それは質問者さんの一番最初の質問に実施済みと書いてあります。

    ここの管理者の方のレスということで注目を集めると認識していただいて、レスの内容には十分注意いただくようお願いします。前にも同じようなお願いをしたことがありましたよね。
    2021年4月16日 9:19
  • 私の回答のカスタム tag ヘルパーとカスタム html ヘルパーのコードは、IUrlHelperFactory と IActionContextAccessor をサービスコンテナーから取得し、 IUrlHelperFactory.GetUrlHelper(ActionContext) メソッドを使って IUrlHelper オブジェクトを取得して利用しています。

    しかし、後でよく調べたら、Microsoft のドキュメント「URL 生成の概念」(URL 下記) に書いてあるように LinkGenerator API を取得して利用する方が良さそうと思いました。

    URL 生成の概念
    https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/routing?view=aspnetcore-3.1#url-generation-concepts

    IActionContextAccessor のサービスへの登録は不要ですしコードも簡単になります。LinkGenerator はサービスコンテナに登録済みのようで、以下のように以下のようにするだけで取得できます。

    using System.Collections.Generic;
    using Microsoft.AspNetCore.Razor.TagHelpers;
    using MvcCoreApp.Models;
    using System.Web;
     
    // LinkGenarator の利用
    using Microsoft.AspNetCore.Routing;
     
    namespace MvcCoreApp.TagHelpers
    {
        public class NaviTagHelper : TagHelper
        {
            private readonly LinkGenerator _linkGenerator;
     
            public NaviTagHelper(LinkGenerator linkGenerator)
            {
                _linkGenerator = linkGenerator;
            }
     
            public IEnumerable<AnchorTagData> Info { get; set; }
     
            public override void Process(TagHelperContext context, 
                                         TagHelperOutput output)
            {
                output.TagName = "ul";
                var @class = "navbar-nav flex-grow-1";
                output.Attributes.SetAttribute("class", @class);
                var content = "";
                foreach (var data in Info)
                {
                    var path = _linkGenerator.GetPathByAction(
                         action: data.Action,
                         controller: data.Controller);
                    content += "<li class=\"nav-item\">" +
                        $"<a class=\"nav-link text-dark\" href=\"{path}\">" +
                        $"{HttpUtility.HtmlEncode(data.Text)}</a>" +
                        "</li>\r\n";
                }
                output.Content.SetHtmlContent(content);
            }
        }
    }

    2021年4月18日 3:51
  • 質問者さん、2021年4月13日 11:07 の書き込み以来無言ですが、回答してますのでそれに対するフィードバックを書いてください。どういう事情があるのか知りませんが、何にせよ自分が立てたスレッドをフォローしないなら最初から質問する必要はないのでは?

    2021年4月19日 6:02
  • SurferOnWww 様、Kumo様

    回答を頂きありがとうございました。

    Startup.cs は、Kumo様が指摘された下記であれば問題なく動作しました。
    なので、回答してマークさせていただきました。

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();     誤り

    services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();   正解


    ただ、ヘルパーの書き方も「asp.net mvc 5.2」と「asp.net core mvc 3.1」では違っていたので、
    SurferOnWww 様 が書いていただいたもの、参考になりました。
     これは次に質問していたかもしれません。

    ありがとぅございました。
    2021年4月24日 17:56
  • 何を今頃って感じなのですが・・・
    2021年4月25日 0:27