คำขอไม่สามารถใช้ได้ในบริบทนี้


113

ฉันใช้โหมดบูรณาการ IIS 7 และได้รับ

คำขอไม่สามารถใช้ได้ในบริบทนี้

เมื่อฉันพยายามเข้าถึงในฟังก์ชันที่เกี่ยวข้องกับ Log4Net ที่เรียกจากApplication_Start. นี่คือบรรทัดของรหัสที่ฉัน

if (HttpContext.Current != null && HttpContext.Current.Request != null)

และมีการโยนข้อยกเว้นสำหรับการเปรียบเทียบครั้งที่สอง

ฉันสามารถตรวจสอบอะไรได้อีกนอกจากตรวจสอบ HttpContext.Current.Request สำหรับ null ??


มีการโพสต์คำถามที่คล้ายกัน @ Request ไม่พร้อมใช้งานในข้อยกเว้นบริบทนี้เมื่อ runnig mvc บน iis7.5

แต่ก็ไม่มีคำตอบที่เกี่ยวข้องเช่นกัน


2
พวกคุณแนะนำให้เพิ่ม try-catch block เป็นตัวเลือกเดียวของฉันได้ไหมถ้าฉันไม่ใช้วิธีแก้ปัญหาอีกสองวิธีตามที่แนะนำในลิงค์จาก Andrew Hare เช่นลอง {if (HttpContext.Current.Request.Headers ["User_info"]! = null) log4net.MDC.Set ("UserInfo", HttpContext.Current.Request.Headers ["User_info"]. ToString ()); } catch () {}
Vishal Seth

คำตอบ:


79

โปรดดูโหมดรวม IIS7: คำขอไม่พร้อมใช้งานในข้อยกเว้นบริบทนี้ใน Application_Start :

ข้อยกเว้น“ คำขอไม่พร้อมใช้งานในบริบทนี้” เป็นข้อผิดพลาดทั่วไปที่คุณอาจได้รับเมื่อย้ายแอปพลิเคชัน ASP.NET ไปยังโหมดรวมบน IIS 7.0 ข้อยกเว้นนี้เกิดขึ้นในการนำเมธอด Application_Start ของคุณไปใช้ในไฟล์ global.asax ถ้าคุณพยายามเข้าถึง HttpContext ของคำร้องขอที่เริ่มต้นแอ็พพลิเคชัน


2
พูดคุยเพิ่มเติมเกี่ยวกับสถานการณ์นี้ได้ที่นี่: stackoverflow.com/questions/1790457/…
jball

6
ขอบคุณ ฉันเคยเห็นลิงค์นั้นมาก่อน ข้อความระบุว่า: "โดยทั่วไปหากคุณกำลังเข้าถึงบริบทคำขอใน Application_Start คุณมีสองทางเลือก: 1) เปลี่ยนรหัสแอปพลิเคชันของคุณเพื่อไม่ใช้บริบทคำขอ (แนะนำ) 2) ย้ายแอปพลิเคชันไปที่โหมดคลาสสิก (ไม่แนะนำ )." พวกเขาไม่มีทางเลือกอื่นหรือ? รหัสบันทึกของฉันเขียนสิ่งต่างๆใน DB เช่นแอปพลิเคชันเริ่มทำงานหากไม่ผ่านการร้องขอควรตั้งค่าฟิลด์เหล่านั้นเป็น null แทนที่จะลบคำสั่งบันทึกของฉันออกทั้งหมด
Vishal Seth

ฉันมีข้อกำหนดการบันทึกประเภทเดียวกันหากบริบทพร้อมใช้งานให้ใช้เพื่อเติมข้อมูลในฐานข้อมูลหากไม่ปล่อยให้ฟิลด์ว่าง (ในกรณีของฉันอย่าเขียนบันทึกลงในตารางบันทึกข้อมูลเดียว แต่จะช่วยได้หากมีวิธีที่ดีในการพิจารณาว่ามีหรือไม่)
Zarepheth

2
ไม่ชอบ แต่การรวมการตรวจสอบในการลองจับเป็นทางเลือกเดียวที่นอกเหนือจากการปรับโครงสร้างใหม่ของรหัสการบันทึกของเรา (และ / หรือแอปทั้งหมด)
Zarepheth

47
มีวิธีใดบ้างที่จะบอกได้ว่าคุณอยู่ในสถานการณ์ที่คำขอจะไม่สามารถใช้ได้หรือไม่? คุณสมบัติบางอย่างของ HttpContext ที่รู้เกี่ยวกับเรื่องนี้? เหตุใดจึงส่งข้อยกเว้นแทนที่จะส่งคืน Nothing เหมือนกับคุณสมบัติอื่น ๆ อีกมากมาย
Joshua Frank

50

เมื่อคุณมีตรรกะการบันทึกแบบกำหนดเองมันค่อนข้างน่ารำคาญที่จะถูกบังคับไม่ให้บันทึก application_start หรือต้องปล่อยให้มีข้อยกเว้นเกิดขึ้นในตัวบันทึก (แม้ว่าจะมีการจัดการ)

ดูเหมือนว่าแทนที่จะทดสอบRequestความพร้อมใช้งานคุณสามารถทดสอบHandlerความพร้อมใช้งานได้: เมื่อไม่มีRequestจึงเป็นเรื่องแปลกที่ยังคงมีตัวจัดการคำขอ และการทดสอบHandlerไม่ได้เพิ่มRequest is not available in this contextข้อยกเว้นที่น่ากลัว

ดังนั้นคุณสามารถเปลี่ยนรหัสของคุณเป็น:

var currContext = HttpContext.Current;
if (currContext != null && currContext.Handler != null)

ระวังในบริบทของโมดูล http Handlerอาจไม่ถูกกำหนดRequestและResponseกำหนดไว้ (ฉันเห็นสิ่งนั้นในเหตุการณ์ BeginRequest) ดังนั้นหากคุณต้องการการบันทึกคำขอ / การตอบกลับในโมดูล http ที่กำหนดเองคำตอบของฉันอาจไม่เหมาะสม


1
ยิ่งไปกว่านั้นข้อเสียที่ระบุไว้แล้วที่นี่ฉันตระหนักว่ามันไม่ใช่วิธีที่จะตอบสนองความต้องการเฉพาะที่อธิบายโดย OP ในความคิดเห็น ดูคำตอบอื่น ๆ ของฉันในหน้านี้
Frédéric

1
นี่เป็นเคล็ดลับสำหรับฉันฉันแค่ต้องตรวจสอบวัตถุที่ร้องขอโดยไม่มีข้อยกเว้น Ty
OverMars

17

นี่เป็นกรณีคลาสสิกมาก: หากคุณต้องตรวจสอบข้อมูลใด ๆ ที่ให้มาโดยอินสแตนซ์ http ให้พิจารณาย้ายรหัสนั้นภายใต้BeginRequestเหตุการณ์

void Application_BeginRequest(Object source, EventArgs e)

นี่เป็นสถานที่ที่เหมาะสมในการตรวจสอบส่วนหัว http สตริงการสืบค้นและอื่น ๆ ... Application_Startสำหรับการตั้งค่าที่ใช้กับเวลาทำงานทั้งหมดของแอปพลิเคชันเช่นการกำหนดเส้นทางตัวกรองการบันทึกและอื่น ๆ

โปรดอย่าใช้วิธีการแก้ปัญหาใด ๆเช่น .ctor แบบคงที่หรือเปลี่ยนไปใช้โหมดคลาสสิกเว้นแต่มีวิธีการย้ายโค้ดจากไม่มีการStart BeginRequestซึ่งควรจะเป็นไปได้สำหรับกรณีส่วนใหญ่ของคุณ


7

เนื่องจากไม่มีบริบทคำขอในไปป์ไลน์ระหว่างการเริ่มแอปอีกต่อไปฉันไม่สามารถจินตนาการได้ว่าจะมีวิธีใดที่จะคาดเดาได้ว่าเซิร์ฟเวอร์ / พอร์ตใดที่คำขอจริงครั้งต่อไปอาจเกิดขึ้น คุณต้องทำใน Begin_Session

นี่คือสิ่งที่ฉันใช้เมื่อไม่ได้อยู่ในโหมดคลาสสิก ค่าใช้จ่ายมีค่าเล็กน้อย

/// <summary>
/// Class is called only on the first request
/// </summary>
private class AppStart
{
    static bool _init = false;
    private static Object _lock = new Object();

    /// <summary>
    /// Does nothing after first request
    /// </summary>
    /// <param name="context"></param>
    public static void Start(HttpContext context)
    {
        if (_init)
        {
            return;
        }
        //create class level lock in case multiple sessions start simultaneously
        lock (_lock)
        {
            if (!_init)
            {
                string server = context.Request.ServerVariables["SERVER_NAME"];
                string port = context.Request.ServerVariables["SERVER_PORT"];
                HttpRuntime.Cache.Insert("basePath", "http://" + server + ":" + port + "/");
                _init = true;
            }
        }
    }
}

protected void Session_Start(object sender, EventArgs e)
{
    //initializes Cache on first request
    AppStart.Start(HttpContext.Current);
}

ขอบคุณสิ่งนี้ทำให้เว็บไซต์ของฉันกลับมาทำงานได้อีกครั้งหลังจากที่มันเกิดอาการนี้ขึ้นมา น่าแปลกที่ฉันไม่ได้เปลี่ยนจาก ASP.NET แบบคลาสสิกในกลุ่มแอป - ฉันยังคงได้รับข้อผิดพลาด การเพิ่มรหัสนี้ (โดยใช้ Interlocked.Exchange (ref int, int)) ช่วยแก้ปัญหาได้
John Källén

1
บรรทัดแรกของคำตอบนี้ (ซ้ำ ... ) ควรถูกลบออก นี่ไม่ใช่โพสต์ที่เชื่อมโยงซ้ำกันคำถามแตกต่างกันมาก เขาไม่ได้ขอเข้าถึงชื่อเซิร์ฟเวอร์ในการเริ่มต้นแอป เขาเต็มใจที่จะมีตรรกะการบันทึกทั่วไปของเขาเท่านั้นที่จะไม่ทิ้งข้อยกเว้นในกรณีพิเศษของ application_start
Frédéric

6

ขึ้นอยู่กับความต้องการโดยละเอียดของ OP ที่อธิบายไว้ในความคิดเห็นมีวิธีแก้ปัญหาที่เหมาะสมกว่า OP ระบุว่าเขาต้องการเพิ่มข้อมูลที่กำหนดเองในบันทึกด้วย log4net ข้อมูลที่เกี่ยวข้องกับคำขอ

แทนที่จะรวมการเรียก log4net แต่ละครั้งไว้ในการเรียกบันทึกส่วนกลางที่กำหนดเองซึ่งจัดการการดึงข้อมูลที่เกี่ยวข้องกับคำขอ (ในการเรียกบันทึกแต่ละครั้ง) log4net มีพจนานุกรมบริบทสำหรับการตั้งค่าข้อมูลเพิ่มเติมที่กำหนดเองเพื่อบันทึก การใช้พจนานุกรมเหล่านั้นช่วยให้สามารถวางตำแหน่งข้อมูลบันทึกคำขอของคุณสำหรับคำขอปัจจุบันที่เหตุการณ์ BeginRequest จากนั้นจึงจะปิดที่เหตุการณ์ EndRequest การเข้าสู่ระบบใด ๆ ระหว่างจะได้รับประโยชน์จากข้อมูลที่กำหนดเองเหล่านั้น

และสิ่งที่ไม่เกิดขึ้นในบริบทคำขอจะไม่พยายามบันทึกข้อมูลที่เกี่ยวข้องกับคำขอทำให้ไม่จำเป็นต้องทดสอบความพร้อมของคำขอ วิธีนี้ตรงกับหลักการที่ Arman McHitaryan แนะนำในคำตอบของเขาคำตอบ

เพื่อให้โซลูชันนี้ใช้งานได้คุณจะต้องมีการกำหนดค่าเพิ่มเติมในส่วนผนวก log4net ของคุณเพื่อให้สามารถบันทึกข้อมูลที่คุณกำหนดเองได้

โซลูชันนี้สามารถนำไปใช้เป็นโมดูลการเพิ่มประสิทธิภาพการบันทึกแบบกำหนดเองได้อย่างง่ายดาย นี่คือโค้ดตัวอย่างสำหรับมัน:

using System;
using System.Web;
using log4net;
using log4net.Core;

namespace YourNameSpace
{
    public class LogHttpModule : IHttpModule
    {
        public void Dispose()
        {
            // nothing to free
        }

        private const string _ipKey = "IP";
        private const string _urlKey = "URL";
        private const string _refererKey = "Referer";
        private const string _userAgentKey = "UserAgent";
        private const string _userNameKey = "userName";

        public void Init(HttpApplication context)
        {
            context.BeginRequest += WebAppli_BeginRequest;
            context.PostAuthenticateRequest += WebAppli_PostAuthenticateRequest;
            // All custom properties must be initialized, otherwise log4net will not get
            // them from HttpContext.
            InitValueProviders(_ipKey, _urlKey, _refererKey, _userAgentKey,
                _userNameKey);
        }

        private void InitValueProviders(params string[] valueKeys)
        {
            if (valueKeys == null)
                return;
            foreach(var key in valueKeys)
            {
                GlobalContext.Properties[key] = new HttpContextValueProvider(key);
            }
        }

        private void WebAppli_BeginRequest(object sender, EventArgs e)
        {
            var currContext = HttpContext.Current;
            currContext.Items[_ipKey] = currContext.Request.UserHostAddress;
            currContext.Items[_urlKey] = currContext.Request.Url.AbsoluteUri;
            currContext.Items[_refererKey] = currContext.Request.UrlReferrer != null ? 
                currContext.Request.UrlReferrer.AbsoluteUri : null;
            currContext.Items[_userAgentKey] = currContext.Request.UserAgent;
        }

        private void WebAppli_PostAuthenticateRequest(object sender, EventArgs e)
        {
            var currContext = HttpContext.Current;
            // log4net doc states that %identity is "extremely slow":
            // http://logging.apache.org/log4net/release/sdk/log4net.Layout.PatternLayout.html
            // So here is some custom retrieval logic for it, so bad, especialy since I
            // tend to think this is a missed copy/paste in that documentation.
            // Indeed, we can find by inspection in default properties fetch by log4net a
            // log4net:Identity property with the data, but it looks undocumented...
            currContext.Items[_userNameKey] = currContext.User.Identity.Name;
        }
    }

    // General idea coming from 
    // http://piers7.blogspot.fr/2005/12/log4net-context-problems-with-aspnet.html
    // We can not use log4net ThreadContext or LogicalThreadContext with asp.net, since
    // asp.net may switch thread while serving a request, and reset the call context
    // in the process.
    public class HttpContextValueProvider : IFixingRequired
    {
        private string _contextKey;
        public HttpContextValueProvider(string contextKey)
        {
            _contextKey = contextKey;
        }

        public override string ToString()
        {
            var currContext = HttpContext.Current;
            if (currContext == null)
                return null;
            var value = currContext.Items[_contextKey];
            if (value == null)
                return null;
            return value.ToString();
        }

        object IFixingRequired.GetFixedObject()
        {
            return ToString();
        }
    }
}

เพิ่มลงในไซต์ของคุณตัวอย่าง IIS 7+ conf:

<system.webServer>
  <!-- other stuff removed ... -->
  <modules>
    <!-- other stuff removed ... -->
    <add name="LogEnhancer" type="YourNameSpace.LogHttpModule, YourAssemblyName" preCondition="managedHandler" />
    <!-- other stuff removed ... -->
  </modules>
  <!-- other stuff removed ... -->
</system.webServer>

และตั้งค่าตัวผนวกเพื่อบันทึกคุณสมบัติเพิ่มเติมเหล่านั้นตัวอย่าง config:

<log4net>
  <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
    <!-- other stuff removed ... -->
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date [%thread] %-5level %logger - %message - %property%newline%exception" />
    </layout>
  </appender>
  <appender name="SqlAppender" type="log4net.Appender.AdoNetAppender">
    <!-- other stuff removed ... -->
    <commandText value="INSERT INTO YourLogTable ([Date],[Thread],[Level],[Logger],[UserName],[Message],[Exception],[Ip],[Url],[Referer],[UserAgent]) VALUES (@log_date, @thread, @log_level, @logger, @userName, @message, @exception, @Ip, @Url, @Referer, @UserAgent)" />
    <!-- other parameters removed ... -->
    <parameter>
      <parameterName value="@userName" />
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%property{userName}" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@Ip"/>
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%property{Ip}" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@Url"/>
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%property{Url}" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@Referer"/>
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%property{Referer}" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@UserAgent"/>
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%property{UserAgent}" />
      </layout>
    </parameter>
  </appender>
  <!-- other stuff removed ... -->
</log4net>

+1 เพื่อชี้ให้เห็นว่าคุณสามารถใช้ HttpContext.CurrentItems ["IP"] แทน HttpContext.Request.UserHostAddress ในกรณีที่ว่างเปล่าขอสิ่งนี้ใช้ได้กับการดึงข้อมูล - ซึ่งช่วยฉันได้ :) ขอบคุณ
Sielu

2

คุณสามารถแก้ไขปัญหาได้โดยไม่ต้องเปลี่ยนไปใช้โหมดคลาสสิกและยังคงใช้ Application_Start

public class Global : HttpApplication
{
   private static HttpRequest initialRequest;

   static Global()
   {
      initialRequest = HttpContext.Current.Request;       
   }

   void Application_Start(object sender, EventArgs e)
   {
      //access the initial request here
   }

ด้วยเหตุผลบางประการประเภทคงที่ถูกสร้างขึ้นด้วยคำขอใน HTTPContext ทำให้คุณสามารถจัดเก็บและนำกลับมาใช้ใหม่ได้ทันทีในเหตุการณ์ Application_Start


ฉันไม่รู้ .. การรันในเครื่องดูเหมือนว่าจะไม่ "เห็น" พอร์ตเมื่อฉันพยายามใช้: initialRequest.Url.GetLeftPart (UriPartial.Authority); จะต้องมองหาวิธีอื่น
justabuzz

แฮ็คอย่างมาก แต่อาจช่วยได้ในบางกรณีที่สิ้นหวัง (ฉันค่อนข้างสมดุลระหว่างการโหวตลงหรือการโหวตขึ้นดังนั้นฉันจึงไม่ใช้การโหวต)
Frédéric

1

ฉันสามารถแก้ไขปัญหา / แฮ็กปัญหานี้ได้โดยย้ายเข้าสู่โหมด "คลาสสิก" จากโหมด "รวม"


0

สิ่งนี้ใช้ได้ผลสำหรับฉัน - หากคุณต้องเข้าสู่ระบบ Application_Start ให้ทำก่อนที่คุณจะแก้ไขบริบท คุณจะได้รับรายการบันทึกโดยไม่มีแหล่งที่มาเช่น:

2019-03-12 09: 35: 43,659 INFO (null) - แอปพลิเคชันเริ่มต้นแล้ว

โดยทั่วไปฉันล็อกทั้ง Application_Start และ Session_Start ดังนั้นฉันจึงเห็นรายละเอียดเพิ่มเติมในข้อความถัดไป

2019-03-12 09: 35: 45,064 INFO ~ / Leads / Leads.aspx - เริ่มเซสชันแล้ว (ในพื้นที่)

        protected void Application_Start(object sender, EventArgs e)
        {
            log4net.Config.XmlConfigurator.Configure();
            log.Info("Application Started");
            GlobalContext.Properties["page"] = new GetCurrentPage();
        }

        protected void Session_Start(object sender, EventArgs e)
        {
            Globals._Environment = WebAppConfig.getEnvironment(Request.Url.AbsoluteUri, Properties.Settings.Default.LocalOverride);
            log.Info(string.Format("Session Started ({0})", Globals._Environment));
        }


0

ใน Visual Studio 2012 เมื่อฉันเผยแพร่โซลูชันด้วยตัวเลือก 'debug' โดยไม่ได้ตั้งใจฉันได้รับข้อยกเว้นนี้ ด้วยตัวเลือก 'ปล่อย' มันไม่เคยเกิดขึ้น หวังว่าจะช่วยได้


-3

คุณสามารถใช้สิ่งต่อไปนี้:

    protected void Application_Start(object sender, EventArgs e)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(StartMySystem));
    }

    private void StartMySystem(object state)
    {
        Log(HttpContext.Current.Request.ToString());
    }

-4

ทำสิ่งนี้ใน global.asax.cs:

protected void Application_Start()
{
  //string ServerSoftware = Context.Request.ServerVariables["SERVER_SOFTWARE"];
  string server = Context.Request.ServerVariables["SERVER_NAME"];
  string port = Context.Request.ServerVariables["SERVER_PORT"];
  HttpRuntime.Cache.Insert("basePath", "http://" + server + ":" + port + "/");
  // ...
}

ใช้งานได้เหมือนมีเสน่ห์ this.Context.Request อยู่ที่นั่น ...

สิ่งนี้คำขอโยนข้อยกเว้นโดยเจตนาตามธง


5
-1: อ่านคำถาม: นี่คือสิ่งที่ล้มเหลว (ด้วย IIS> = 7 และโหมดบูรณาการ)
Richard

นี่คือสิ่งที่จะเกิดขึ้นเมื่อโจรสลัดหลุดงานและพยายามเขียนโปรแกรมด้วยตัวเอง :) ไม่มีความผิดผู้ชาย;)
Arman McHitarian
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.