Перехват необработанных исключений в пользовательских элементах управления ASP.NET

Я динамически загружаю пользовательские элементы управления, добавляя их в коллекцию элементов управления веб-формы.

Я хотел бы скрыть пользовательские элементы управления, если они вызывают необработанное исключение при рендеринге.

Итак, я попытался подключиться к событию Error каждого UserControl, но кажется, что это событие никогда не срабатывает для UserControls, как для класса Page.

Погуглил, и это не кажется многообещающим. Есть идеи здесь?

Ответов (7)

Решение

mmilic, исходя из вашего ответа на мою предыдущую идею ..

Никакой дополнительной логики не требуется! В том-то и дело, что вы ничего не делаете с рассматриваемыми классами, просто оборачиваете их в пузырчатую пленку для создания экземпляров! :)

Хорошо, я собирался просто выделить пункт, но я хотел увидеть эту работу сам, поэтому я сколотил очень грубый код, но концепция есть, и, похоже, она работает.

ИЗВИНЕНИЯ ЗА ДЛИТЕЛЬНУЮ СЛУЖБУ

SafeLoader

По сути, это будет тот «пузырь», о котором я упоминал ... Он получит HTML-код элементов управления, улавливая любые ошибки, возникающие во время рендеринга.

public class SafeLoader
{
    public static string LoadControl(Control ctl)
    {
        // In terms of what we could do here, its down
        // to you, I will just return some basic HTML saying
        // I screwed up.
        try
        {
            // Get the Controls HTML (which may throw)
            // And store it in our own writer away from the
            // actual Live page.
            StringWriter writer = new StringWriter();
            HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);
            ctl.RenderControl(htmlWriter);

            return writer.GetStringBuilder().ToString();
        }
        catch (Exception)
        {
            string ctlType = ctl.GetType().Name;
            return "<span style=\"color: red; font-weight:bold; font-size: smaller;\">" + 
                "Rob + Controls = FAIL (" + 
                ctlType + " rendering failed) Sad face :(</span>";
        }
    }
}

И некоторые элементы управления ..

Хорошо, я просто издевался над двумя элементами управления здесь, один будет бросать, другой будет отображать мусор. Укажите здесь, мне плевать. Они будут заменены вашими пользовательскими элементами управления.

BadControl

public class BadControl : WebControl
{
    protected override void Render(HtmlTextWriter writer)
    {
        throw new ApplicationException("Rob can't program controls");
    }
}

GoodControl

public class GoodControl : WebControl
{
    protected override void Render(HtmlTextWriter writer)
    {
        writer.Write("<b>Holy crap this control works</b>");
    }
}

Страница

Хорошо, давайте посмотрим на "тестовую" страницу. Здесь я просто создаю экземпляры элементов управления, беру их html и вывожу его, я буду следовать своим мыслям о поддержке дизайнеров и т. Д.

Отстающий код страницы

    protected void Page_Load(object sender, EventArgs e)
    {
        // Create some controls (BadControl will throw)
        string goodHtml = SafeLoader.LoadControl(new BadControl());
        Response.Write(goodHtml);

        string badHtml = SafeLoader.LoadControl(new GoodControl());
        Response.Write(badHtml);
    }

Мысли

Хорошо, я знаю, о чем вы думаете: «Эти элементы управления создаются программно, а как насчет поддержки дизайнера? Я потратил чертовски много часов на то, чтобы эти элементы управления были удобны для дизайнера, а теперь вы возитесь с моим моджо».

Хорошо, поэтому я еще не тестировал это (вероятно, через минуту!), Но идея здесь состоит в том, чтобы переопределить метод CreateChildControls для страницы и взять экземпляр каждого элемента управления, добавленного в форму, и запустить его через SafeLoader. Если код прошел, вы можете добавить его в коллекцию Controls как обычно, если нет, вы можете создать ошибочные литералы или что-то в этом роде, на ваше усмотрение, мой друг.

Наконец-то..

Опять же, извините за длинный пост, но я хотел получить здесь код, чтобы мы могли это обсудить :) Надеюсь, это поможет продемонстрировать мою идею :)

Обновлять

Протестировано путем добавления элемента управления в конструктор и переопределения метода CreateChildControls с его помощью, работает нормально, может потребоваться некоторая очистка, чтобы все выглядело лучше, но я оставлю это вам;)

protected override void CreateChildControls()
{
    // Pass each control through the Loader to check
    // its not lame
    foreach (Control ctl in Controls)
    {
        string s = SafeLoader.LoadControl(ctl);
        // If its bad, smack it downnnn!
        if (s == string.Empty)
        {
            ctl.Visible = false; // Prevent Rendering
            string ctlType = ctl.GetType().Name;
            Response.Write("<b>Problem Occurred Rendering " + 
                ctlType + " '" + ctl.ID + "'.</b>");
        }
    }
}

Наслаждаться!

Как насчет добавления нового подкласса UserControl, который обрабатывает ошибки в методах рендеринга и загрузки (чтобы они скрывались по вашему желанию), а затем наследует его для ваших пользовательских элементов управления?

Я использовал подход @ Keith, но проблема в том, что элемент управления отображается до тех пор, пока не будет выбрано исключение, что может привести к открытию тегов HTML. Я также отображаю информацию об исключении в элементе управления, если он находится в режиме отладки.

  protected override void Render(System.Web.UI.HtmlTextWriter writer)
  {
     try
     {
        // Render the module to a local a temporary writer so that if an Exception occurs
        // the control is not halfway rendered - "it is all or nothing" proposition
        System.IO.StringWriter sw = new System.IO.StringWriter();
        System.Web.UI.HtmlTextWriter htw = new System.Web.UI.HtmlTextWriter(sw);
        base.Render(htw);

        // We made it!  Copy the Control Render over
        writer.Write(sw.GetStringBuilder().ToString());
     }
     catch (System.Exception ex)
     {
        string message = string.Format("Error Rendering Control {0}\n", ID);
        Log.Error(message, ex);
        if (Page.IsDebug)
           writer.Write(string.Format("{0}<br>Exception:<br><pre>{1}\n{2}</pre>", message, ex.Message, ex.StackTrace));
     }
  }

Я не уверен, что понимаю ваш ответ . Как вы загружаете элементы управления и добавляете их в коллекцию элементов управления?

В этом весь смысл бита, добавленного в раздел «Обновление». У вас есть возможность использовать SafeLoader где угодно.

Я не уверен, почему вы считаете, что у вас нет доступа / контроля над HTML? Цель SafeLoader состоит в том, чтобы вам было все равно, что такое html, вы просто пытаетесь «вывести» элемент управления (внутри «пузыря») и определить, нормально ли он загружается в текущем состоянии.

Если это так (т.е. возвращается html), вы можете делать с ним все, что вам нравится, выводить html, добавлять элемент управления в коллекцию элементов управления, что угодно!

Если нет, то опять же, вы можете делать то, что вам нравится, отображать сообщение об ошибке, генерировать настраиваемое исключение ... Выбор за вами!

Надеюсь, это поможет вам прояснить ситуацию, если нет, то кричите, пожалуйста :)

Это интересная проблема ... Я все еще довольно свеж, когда дело доходит до настраиваемых элементов управления и т. Д., Но вот мои мысли (не стесняйтесь комментировать / исправлять людей!) .. (Я вроде как думаю / пишу здесь вслух!)

  • Если во время рендеринга возникает ошибка, в некоторых случаях не будет ли слишком поздно? (поскольку некоторые элементы управления HTML уже могли быть отправлены на Writer и выведены).
  • Следовательно, не лучше ли обернуть метод Render пользовательского элемента управления, а вместо того, чтобы передать ему ссылку на «Live» HtmlTextWriter, вы передадите свой собственный, перехватите любые исключения, возникающие в этом маленьком «пузыре» безопасности, если все пойдет хорошо , вы затем передаете полученный HTML-код фактическому HtmlTextWriter?
  • Эту логику, вероятно, можно было бы привязать к универсальному классу-оболочке, который вы использовали бы для динамической загрузки / рендеринга элементов управления во время выполнения.
  • Если возникнут какие-либо ошибки, в вашем распоряжении будет вся необходимая информация! (т.е. контрольные ссылки и т. д.).

Только мои мысли, пламя прочь! : D;)

Global.asax и Application_Error?

http://www.15seconds.com/issue/030102.htm

Или событие Page_Error только на отдельной странице:

http://support.microsoft.com/kb/306355

void Page_Load(object sender, System.EventArgs e)
{
    throw(new ArgumentNullException());
}

public void Page_Error(object sender,EventArgs e)
{
    Exception objErr = Server.GetLastError().GetBaseException();
    string err =    "<b>Error Caught in Page_Error event</b><hr><br>" + 
                    "<br><b>Error in: </b>" + Request.Url.ToString() +
                    "<br><b>Error Message: </b>" + objErr.Message.ToString()+
                    "<br><b>Stack Trace:</b><br>" + 
                      objErr.StackTrace.ToString();
    Response.Write(err.ToString());
    Server.ClearError();
}

Кроме того, у Карла Сегуина (привет, Карл!) Было сообщение об использовании HttpHandler вместо этого:

http://codebetter.com/blogs/karlseguin/archive/2006/06/12/146356.aspx

(Не уверен, какое разрешение на его воспроизведение, но если вы хотите написать ответ, вы получили мой голос за)

В зависимости от того, где возникают ваши ошибки, вы можете сделать что-то вроде ...

public abstract class SilentErrorControl : UserControl
{
    protected override void Render( HtmlTextWriter writer )
    {
        //call the base's render method, but with a try catch
        try { base.Render( writer ); }
        catch ( Exception ex ) { /*do nothing*/ }
    }
}

Затем унаследуйте SilentErrorControl вместо UserControl.