web-dev-qa-db-ja.com

ブラウザにasp.netアプリケーションの最新のjsおよびcssファイルを取得させる

一部のブラウザーは、jsファイルとcssファイルをキャッシュしますが、強制しない限り更新に失敗します。最も簡単な方法は何ですか。

このソリューションを実装したところ、うまくいくようです。

ページでバージョン変数を宣言します

  public string version { get; set; }

Web.configキーからバージョン番号を取得します

 version = ConfigurationManager.AppSettings["versionNumber"];

Aspxページで、javascriptとスタイルシートを次のように呼び出します

<script src="scripts/myjavascript.js?v=<%=version %>" type="text/javascript"></script>
<link href="styles/mystyle.css?v=<%=version %>" rel="stylesheet" type="text/css" />

そのため、web.configでバージョンを1.0から1.1に設定すると、ブラウザは最新のファイルをダウンロードします。これにより、ユーザーとユーザーのフラストレーションを軽減できます。

うまく機能する別の解決策はありますか、またはこれによりWebサイトに予期しない問題が発生しますか?

94
kiev

これを解決するには、スクリプトのクエリパラメーターとして最後に変更されたタイムスタンプを追加します。

これを拡張メソッドで行い、CSHTMLファイルで使用しました。 注:この実装は、1分間のタイムスタンプをキャッシュするため、ディスクをあまりスラッシングしません。

拡張メソッドは次のとおりです。

public static class JavascriptExtension {
    public static MvcHtmlString IncludeVersionedJs(this HtmlHelper helper, string filename) {
        string version = GetVersion(helper, filename);
        return MvcHtmlString.Create("<script type='text/javascript' src='" + filename + version + "'></script>");
    }

    private static string GetVersion(this HtmlHelper helper, string filename)
    {
        var context = helper.ViewContext.RequestContext.HttpContext;

        if (context.Cache[filename] == null)
        {
            var physicalPath = context.Server.MapPath(filename);
            var version = $"?v={new System.IO.FileInfo(physicalPath).LastWriteTime.ToString("MMddHHmmss")}";
            context.Cache.Add(filename, version, null,
              DateTime.Now.AddMinutes(5), TimeSpan.Zero,
              CacheItemPriority.Normal, null);
            return version;
        }
        else
        {
            return context.Cache[filename] as string;
        }
    }
}

そして、CSHTMLページで:

 @Html.IncludeVersionedJs("/MyJavascriptFile.js")

レンダリングされたHTMLでは、これは次のように表示されます。

 <script type='text/javascript' src='/MyJavascriptFile.js?20111129120000'></script>
70
Adam Tegen

ソリューションが機能します。実際、非常に人気があります。

スタックオーバーフローでさえ、同様の方法を使用します。

<link rel="stylesheet" href="http://sstatic.net/so/all.css?v=6184"> 

どこ v=6184はおそらくSVNリビジョン番号です。

26
Daniel Vassallo

ASP.NET Core(MVC 6)では、asp-append-versionタグヘルパー:

<script src="scripts/myjavascript.js" asp-append-version="true"></script>
<link href="styles/mystyle.css rel="stylesheet" asp-append-version="true" />
20
metalheart

JS/CSSにバンドルを使用する場合、ASP.NET MVCがこれを処理します。 GUID=の形式でバージョン番号をバンドルに自動的に追加し、これを更新しますGUIDファイルに変更があります)。

これは、大量のJS/CSSファイルがある場合にも役立ちます。これは、コンテンツのロード時間を大幅に改善できるためです。

こちらを参照

18
jonesy827

このためのasp.netには組み込みの方法があります:bundling。ただそれを使用してください。各新しいバージョンには、一意の接尾辞「?v = XXXXXXX」が付きます。デバッグモードでは、web.configのmake設定をオンに切り替えるためのバンドルはオフです。

<system.web>
    <compilation debug="false" />
</system.web>

または、メソッドRegisterBundles(BundleCollection bundles)に追加します。

BundleTable.EnableOptimizations = true;

例えば:

BundleConfig.cs:

bundles.Add(new ScriptBundle("~/Scripts/myjavascript.js")
                .Include("~/Scripts/myjavascript.js"));

bundles.Add(new StyleBundle("~/Content/mystyle.css")
                .Include("~/Content/mystyle.css"));

_Layout.cshtml:

@Scripts.Render("~/Scripts/myjavascript.js")
@Styles.Render("~/Content/mystyle.css")
11
Alex Tkachuk

キャッシュを無効にするためのパスを一意にするために、単純な1つのライナーが必要でした。これは私のために働いた:

<script src="scripts/main.js?bust_js_cache=<%=System.IO.File.GetLastWriteTime(Server.MapPath("scripts/main.js")).ToString("HH:mm:ss")%>" type="text/javascript"></script>

ファイルが最後にページにロードされてから変更されている場合、ブラウザは更新されたファイルをプルします。

last modifiedファイルから.jsスタンプを生成し、アクセスしにくいバージョンではなく、そこに保持します。

<script src="scripts/main.js?bust_js_cache=10:18:38" type="text/javascript"></script>

別のオプションは、ファイルのチェックサムを取得することです。

4
sniperd

Adam Tegan's answer に基づいており、Webフォームアプリケーションで使用するために変更されています。

.csクラスコード:

public static class FileUtility
{
    public static string SetJsVersion(HttpContext context, string filename) {
        string version = GetJsFileVersion(context, filename);
        return filename + version;
    }

    private static string GetJsFileVersion(HttpContext context, string filename)
    {
        if (context.Cache[filename] == null)
        {
            string filePhysicalPath = context.Server.MapPath(filename);

            string version = "?v=" + GetFileLastModifiedDateTime(context, filePhysicalPath, "yyyyMMddhhmmss");

            return version;
        }
        else
        {
            return string.Empty;
        }
    }

    public static string GetFileLastModifiedDateTime(HttpContext context, string filePath, string dateFormat)
    {
        return new System.IO.FileInfo(filePath).LastWriteTime.ToString(dateFormat);
    }
}

Aspxマークアップで:

<script type="text/javascript" src='<%= FileUtility.SetJsVersion(Context,"/js/exampleJavaScriptFile.js") %>'></script>

レンダリングされたHTMLでは、次のように表示されます

<script type="text/javascript" src='/js/exampleJavaScriptFile.js?v=20150402021544'></script>
4
Bryan

興味深いことに、このサイトには、フェールセーフであるべきであるにもかかわらず、いくつかのプロキシ設定に関連して説明するアプローチに問題があります。

これを確認してください Meta Stack Overflow ディスカッション。

そのため、GETパラメーターを使用して更新するのではなく、実際のファイル名を使用する方が理にかなっている場合があります。

href="/css/scriptname/versionNumber.css" 

実際にファイルを作成するか、そのファイルのURL書き換えを作成する必要があるため、これはより多くの作業です。

4
Pekka 웃

これには、質問のopによって与えられる答えよりも簡単な答えがあります(アプローチは同じです)。

Web.configでキーを定義します。

<add key="VersionNumber" value="06032014"/>

Aspxページから直接appsettingsを呼び出します。

<link href="styles/navigation.css?v=<%=ConfigurationManager.AppSettings("VersionNumber")%>" rel="stylesheet" type="text/css" />
4
JackArbiter

ASP.NET 5/MVC 6/vNextで機能するアプローチを次に示します。

ステップ1:ファイルの最終書き込み時間を返すクラスを作成します。このスレッドの他の回答と同様です。これには、ASP.NET 5(またはその他の)依存性注入が必要であることに注意してください。

public class FileVersionService
{
    private IHostingEnvironment _hostingEnvironment;
    public FileVersionService(IHostingEnvironment hostingEnvironment)
    {
        _hostingEnvironment = hostingEnvironment;
    }

    public string GetFileVersion(string filename)
    {
       var path = string.Format("{0}{1}", _hostingEnvironment.WebRootPath, filename);
       var fileInfo = new FileInfo(path);
       var version = fileInfo.LastWriteTimeUtc.ToString("yyyyMMddhhmmssfff");
       return version;
     }
}

ステップ2:内部に挿入するサービスを登録しますstartup.cs

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<FileVersionService>();
    ...
}

ステップ3:次に、ASP.NET 5では、次のように_ Layout.cshtmlなどのレイアウトビューにサービスを直接挿入することができます。

@inject Namespace.Here.FileVersionService fileVersionService
<!DOCTYPE html>
<html lang="en" class="@ViewBag.HtmlClass">
<head>
    ...
    <link href="/css/[email protected]("\\css\\styles.css")" rel="stylesheet" />
    ...
</head>
<body>
    ...
</body>

物理パスをより適切に組み合わせて、構文とより一貫したスタイルでファイル名を処理するために行うことができるいくつかの最後の仕上げがありますが、これは出発点です。 ASP.NET 5への移行に役立つことを願っています。

3
Ender2050

上記の答え から始めて、CSSファイルでもヘルパーが動作するようにコードを少し変更し、ビルドを行うときだけでなく、ファイルを変更するたびにバージョンを追加します

public static class HtmlHelperExtensions
{
    public static MvcHtmlString IncludeVersionedJs(this HtmlHelper helper, string filename)
    {
        string version = GetVersion(helper, filename);
        return MvcHtmlString.Create("<script type='text/javascript' src='" + filename + version + "'></script>");
    }

    public static MvcHtmlString IncludeVersionedCss(this HtmlHelper helper, string filename)
    {
        string version = GetVersion(helper, filename);
        return MvcHtmlString.Create("<link href='" + filename + version + "' type ='text/css' rel='stylesheet'/>");
    }

    private static string GetVersion(this HtmlHelper helper, string filename)
    {
        var context = helper.ViewContext.RequestContext.HttpContext;
        var physicalPath = context.Server.MapPath(filename);
        var version = "?v=" +
        new System.IO.FileInfo(physicalPath).LastWriteTime
        .ToString("yyyyMMddHHmmss");
        context.Cache.Add(physicalPath, version, null,
          DateTime.Now.AddMinutes(1), TimeSpan.Zero,
          CacheItemPriority.Normal, null);

        if (context.Cache[filename] == null)
        {
            context.Cache[filename] = version;
            return version;
        }
        else
        {
            if (version != context.Cache[filename].ToString())
            {
                context.Cache[filename] = version;
                return version;
            }
            return context.Cache[filename] as string;
        }
    }
}
2
Sergi Mulà

<?php $Rand_no = Rand(10000000, 99999999)?> <script src="scripts/myjavascript.js?v=<?=$Rand_no"></script>

これはすべてのブラウザで機能します。ここでは、ランダムな番号を生成するためにPHPを使用しました。独自のサーバー側言語を使用できます。`

2
Mukesh Rai

私は、aspnet MVC 4サイトで少し異なる手法を使用しました。

_ViewStart.cshtml:

@using System.Web.Caching
@using System.Web.Hosting
@{
    Layout = "~/Views/Shared/_Layout.cshtml";
    PageData.Add("scriptFormat", string.Format("<script src=\"{{0}}?_={0}\"></script>", GetDeployTicks()));
}

@functions
{

    private static string GetDeployTicks()
    {
        const string cacheKey = "DeployTicks";
        var returnValue = HttpRuntime.Cache[cacheKey] as string;
        if (null == returnValue)
        {
            var absolute = HostingEnvironment.MapPath("~/Web.config");
            returnValue = File.GetLastWriteTime(absolute).Ticks.ToString();
            HttpRuntime.Cache.Insert(cacheKey, returnValue, new CacheDependency(absolute));
        }
        return returnValue;
    }
}

次に、実際のビューで:

 @Scripts.RenderFormat(PageData["scriptFormat"], "~/Scripts/Search/javascriptFile.min.js")
2
Anthony Wolfe

以前の提案を簡素化し、.NET Webフォーム開発者にコードを提供します。

これは、リソースへのファイルパスで相対( "〜/")および絶対URLの両方を受け入れます。

静的拡張機能クラスファイルに、次を入力します。

public static string VersionedContent(this HttpContext httpContext, string virtualFilePath)
{
    var physicalFilePath = httpContext.Server.MapPath(virtualFilePath);
    if (httpContext.Cache[physicalFilePath] == null)
    {
        httpContext.Cache[physicalFilePath] = ((Page)httpContext.CurrentHandler).ResolveUrl(virtualFilePath) + (virtualFilePath.Contains("?") ? "&" : "?") + "v=" + File.GetLastWriteTime(physicalFilePath).ToString("yyyyMMddHHmmss");
    }
    return (string)httpContext.Cache[physicalFilePath];
}

次に、マスターページで次のように呼び出します。

<link type="text/css" rel="stylesheet" href="<%= Context.VersionedContent("~/styles/mystyle.css") %>" />
<script type="text/javascript" src="<%= Context.VersionedContent("~/scripts/myjavascript.js") %>"></script>
1
Jason Ellingson

以下に示すように、ファイルの変更時刻を取得します

private static string GetLastWriteTimeForFile(string pathVal)
    {
        return System.IO.File.GetLastWriteTime(HostingEnvironment.MapPath(pathVal)).ToFileTime().ToString();
    }

入力をクエリ文字列として追加します

public static string AppendDateInFile(string pathVal)
    {
        var patheWithDate = new StringBuilder(pathVal);
        patheWithDate.AppendFormat("{0}x={1}",
                               pathVal.IndexOf('?') >= 0 ? '&' : '?',
                               GetLastWriteTimeForFile(pathVal));
        return patheWithDate.ToString();
    }

これをマークアップから呼び出します。

MVC拡張ヘルパーアプローチ

拡張メソッドを追加する

namespace TNS.Portal.Helpers
{
    public static class ScriptExtensions
    {
        public static HtmlString QueryStringScript<T>(this HtmlHelper<T> html, string path)
        {
            var file = html.ViewContext.HttpContext.Server.MapPath(path);
            DateTime lastModified = File.GetLastWriteTime(file);
            TagBuilder builder = new TagBuilder("script");
            builder.Attributes["src"] = path + "?modified=" + lastModified.ToString("yyyyMMddhhmmss");
            return new HtmlString(builder.ToString());
        }

       public static HtmlString QueryStringStylesheet<T>(this HtmlHelper<T> html, string path)
       {
        var file = html.ViewContext.HttpContext.Server.MapPath(path);
        DateTime lastModified = File.GetLastWriteTime(file);
        TagBuilder builder = new TagBuilder("link");
        builder.Attributes["href"] = path + "?modified=" + lastModified.ToString("yyyyMMddhhmmss");
        builder.Attributes["rel"] = "stylesheet";
        return new HtmlString(builder.ToString());
      }

    }
}

この名前空間をweb.configに追加します

<system.web.webPages.razor>
    <Host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization"/>
        <add namespace="System.Web.Routing" />
        <add namespace="TNS.Portal" />
        <add namespace="TNS.Portal.Helpers" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

ビューで使用する

@Html.QueryStringScript("/Scripts/NPIAjaxCalls.js")
@Html.QueryStringStylesheet("/Content/StyledRadio.css")
1
Lijo

スクリプトまたはスタイルのDefaultTagFormatプロパティをオーバーライドできます。

Scripts.DefaultTagFormat = @"<script src=""{0}?v=" + ConfigurationManager.AppSettings["pubversion"] + @"""></script>";
Styles.DefaultTagFormat = @"<link href=""{0}?v=" + ConfigurationManager.AppSettings["pubversion"] + @""" rel=""stylesheet""/>";
0
phoenix

ASP.Net Ajaxアプリケーションでこの問題を解決するために、拡張機能を作成し、マスターページで呼び出しました。

詳細については、 link をご覧ください。

0
Kasim Husaini

上記の答え に基づいて、CSSファイルとJSファイルで動作する小さな拡張クラスを作成しました。

public static class TimestampedContentExtensions
{
    public static string VersionedContent(this UrlHelper helper, string contentPath)
    {
        var context = helper.RequestContext.HttpContext;

        if (context.Cache[contentPath] == null)
        {
            var physicalPath = context.Server.MapPath(contentPath);
            var version = @"v=" + new FileInfo(physicalPath).LastWriteTime.ToString(@"yyyyMMddHHmmss");

            var translatedContentPath = helper.Content(contentPath);

            var versionedContentPath =
                contentPath.Contains(@"?")
                    ? translatedContentPath + @"&" + version
                    : translatedContentPath + @"?" + version;

            context.Cache.Add(physicalPath, version, null, DateTime.Now.AddMinutes(1), TimeSpan.Zero,
                CacheItemPriority.Normal, null);

            context.Cache[contentPath] = versionedContentPath;
            return versionedContentPath;
        }
        else
        {
            return context.Cache[contentPath] as string;
        }
    }
}

次のようなものを書く代わりに:

<link href="@Url.Content(@"~/Content/bootstrap.min.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content(@"~/Scripts/bootstrap.min.js")"></script>

あなたは今書くことができます:

<link href="@Url.VersionedContent(@"~/Content/bootstrap.min.css")" rel="stylesheet" type="text/css" />
<script src="@Url.VersionedContent(@"~/Scripts/bootstrap.min.js")"></script>

つまり単にUrl.Content with Url.VersionedContent

生成されたURLは次のようになります。

<link href="/Content/bootstrap.min.css?v=20151104105858" rel="stylesheet" type="text/css" />
<script src="/Scripts/bootstrap.min.js?v=20151029213517"></script>

拡張クラスを使用する場合、MapPathは物理ファイルではないため、contentPath呼び出しが機能しない場合にエラー処理を追加できます。

0
Uwe Keim

この方法で行う主な問題は、主に、cssまたはjsファイルに変更を加えるたびにコードのバージョン番号を更新することを忘れないでください。

それを行うためのおそらくより良い方法は、次のように、cssまたはjsファイルのそれぞれに保証された一意のパラメーターを設定することです。

<script src="scripts/myjavascript.js?_=<%=DateTime.Now.Ticks%>" type="text/javascript"></script>
<link href="styles/mystyle.css?_=<%=DateTime.Now.Ticks%>" rel="stylesheet" type="text/css" />

これにより、サーバーから毎回ファイルが要求されるようになります。また、これらのファイルはキャッシュされず、毎回不要な帯域幅を使用するため、ページのロード時にサイトのパフォーマンスが低下します。

基本的に、変更が行われるたびにバージョン番号を更新することを忘れないようにすれば、それをどのように行っているかを理解できます。

0

同様の方法を使用して、各ページを変更せずに同じ操作を実行します。マスターファイルであるPreRenderイベントが追加されました。ロジックを1か所に保持し、jsファイルとcssファイルの両方に適用できます。

protected void Page_PreRender(object sender, EventArgs e)
    {
        HtmlLink link = null;
        LiteralControl script = null;


        foreach (Control c in Header.Controls)
        {
            //StyleSheet add version
            if (c is HtmlLink)
            {
                link = c as HtmlLink;


                if (link.Href.EndsWith(".css", StringComparison.InvariantCultureIgnoreCase))
                {
                    link.Href += string.Format("?v={0}", ConfigurationManager.AppSettings["agVersion"]);
                }

            }

            //Js add version
            if (c is LiteralControl)
            {
                script = c as LiteralControl;

                if (script.Text.Contains(".js"))
                {
                    var foundIndexes = new List<int>();


                    for (int i = script.Text.IndexOf(".js\""); i > -1; i = script.Text.IndexOf(".js\"", i + 1))
                    {

                        foundIndexes.Add(i);
                    }

                    for (int i = foundIndexes.Count - 1; i >= 0; i--)
                    {

                        script.Text = script.Text.Insert(foundIndexes[i] + 3, string.Format("?v={0}", ConfigurationManager.AppSettings["agVersion"]));
                    }
                }

            }

        }
    }
0
Jay