How to revert an HttpError ConfigurationElement ba

2019-06-16 07:36发布

问题:

Suppose I have the following <httpErrors> collection in a web.config:

<httpErrors>
</httpErrors>

Yep, nice 'n empty.

And in IIS 7, my HTTP errors page looks like this:

Beautiful! (I've highlighted 404 simply because that's the example I'll use in a sec).

Now, I run the following code:

errorElement["statusCode"] = 404;
errorElement["subStatusCode"] = -1;
errorElement["path"] = "/404.html";
httpErrorsCollection.Add(errorElement);

Lovely. I now have, as expected this in my web.config:

<httpErrors>
    <remove statusCode="404" subStatusCode="-1" />
    <error statusCode="404" subStatusCode="-1" prefixLanguageFilePath="" path="/404.html" />
</httpErrors>

Couldn't be happier. Now, in IIS 7 my HTTP errors section looks, as expected, like below:

Life couldn't be sweeter at this point. Now, down the line, I want to programmatically revert my 404 error back to the state shown in the original screenshot. Logic dictates that I should remove my new error:

httpErrorsCollection.Remove(errorElement);

But alas, if I do this, my web.config looks a lot like this:

    <httpErrors>
        <remove statusCode="404" subStatusCode="-1" />
    </httpErrors>

And my IIS looks a bit like this:

This is expected because of my web.config - but how, using ServerManager and all it's useful IIS 7 API, do I remove the httpError element entirely and revert my web.config back to:

<httpErrors>
</httpErrors>

Any ideas?

回答1:

In IIS7 and above under Management section, we can see an icon named Configuration Editor, double click and expand to

system.webserver-->webdav-->httpErrors

Right click on Default path, under

section-->Click on Revert to Parent

Then restart the website

The changes will be reverted back



回答2:

I've bumped into this before. The only way I could make this work was to call RevertToParent() on the system.webServer/httpErrors section and commit the changes before making any changes, e.g.:

ConfigurationSection httpErrorsSection = 
           config.GetSection("system.webServer/httpErrors");

// Save a copy of the errors collection first (see pastebin example)
 httpErrorsCollectionLocal = 
            CopyLocalErrorCollection(httpErrorsSection.GetCollection()
            .Where(e => e.IsLocallyStored)
            .ToList<ConfigurationElement>());

httpErrorsSection.RevertToParent();
serverManager.CommitChanges();

You have to commit after RevertToParent() is called because the errors collection remains intact untilCommitChanges()` is called.

You then have to re-add the saved copy of the local errors collection removing or updating the custom errors as they are re-added.

I've pasted a full working example below. The code works on the site root web.config but with a bit of tinkering you could add support for web.config files in subfolders:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Web.Administration;

namespace IIS7_HttpErrorSectionClearing
{
  class Program
  {
    static void Main(string[] args)
    {
      long siteId = 60001;

      // Add some local custom errors
      CustomErrorManager.SetCustomError(siteId, 404, -1, ResponseMode.File, @"D:\websites\60001\www\404.html");
      CustomErrorManager.SetCustomError(siteId, 404, 1, ResponseMode.File, @"D:\websites\60001\www\404-1.html");
      CustomErrorManager.SetCustomError(siteId, 404, 5, ResponseMode.File, @"D:\websites\60001\www\404-5.html");

      // Change existing local custom error
      CustomErrorManager.SetCustomError(siteId, 404, 1, ResponseMode.ExecuteURL, "/404-5.aspx");

      // Revert to inherited
      CustomErrorManager.RevertCustomError(siteId, 404, 5);
      CustomErrorManager.RevertCustomError(siteId, 404, -1);
      CustomErrorManager.RevertCustomError(siteId, 404, 1);
    }
  }

  public enum ResponseMode
  {
    File,
    ExecuteURL,
    Redirect
  }  

  public static class CustomErrorManager
  {
    public static void RevertCustomError(long siteId, int statusCode, int subStatusCode)
    {
      List<Error> httpErrorsCollectionLocal = CopyLocalsAndRevertToInherited(siteId);
      int index = httpErrorsCollectionLocal.FindIndex(e => e.StatusCode == statusCode && e.SubStatusCode == subStatusCode);
      if(index > -1)
      {
        httpErrorsCollectionLocal.RemoveAt(index);
      }
      PersistLocalCustomErrors(siteId, httpErrorsCollectionLocal);
    }

    public static void SetCustomError(long siteId, long statusCode, int subStatusCode, 
                                          ResponseMode responseMode, string path)
    {
      SetCustomError(siteId, statusCode, subStatusCode, responseMode, path, null);
    }

    public static void SetCustomError(long siteId, long statusCode, int subStatusCode, 
                                          ResponseMode responseMode, string path, string prefixLanguageFilePath)
    {
      List<Error> httpErrorsCollectionLocal = CopyLocalsAndRevertToInherited(siteId);
      AddOrUpdateError(httpErrorsCollectionLocal, statusCode, subStatusCode, responseMode, path, prefixLanguageFilePath);
      PersistLocalCustomErrors(siteId, httpErrorsCollectionLocal);
    }

    private static void PersistLocalCustomErrors(long siteId, List<Error> httpErrorsCollectionLocal)
    {
      using (ServerManager serverManager = new ServerManager())
      {
        Site site = serverManager.Sites.Where(s => s.Id == siteId).FirstOrDefault();
        Configuration config = serverManager.GetWebConfiguration(site.Name);
        ConfigurationSection httpErrorsSection = config.GetSection("system.webServer/httpErrors");
        ConfigurationElementCollection httpErrorsCollection = httpErrorsSection.GetCollection();

        foreach (var localError in httpErrorsCollectionLocal)
        {
          ConfigurationElement remove = httpErrorsCollection.CreateElement("remove");
          remove["statusCode"] = localError.StatusCode;
          remove["subStatusCode"] = localError.SubStatusCode;
          httpErrorsCollection.Add(remove);

          ConfigurationElement add = httpErrorsCollection.CreateElement("error");
          add["statusCode"] = localError.StatusCode;
          add["subStatusCode"] = localError.SubStatusCode;
          add["responseMode"] = localError.ResponseMode;
          add["path"] = localError.Path;
          add["prefixLanguageFilePath"] = localError.prefixLanguageFilePath;
          httpErrorsCollection.Add(add);
        }
        serverManager.CommitChanges();
      }
    }

    private static List<Error> CopyLocalsAndRevertToInherited(long siteId)
    {
      List<Error> httpErrorsCollectionLocal;
      using (ServerManager serverManager = new ServerManager())
      {
        Site site = serverManager.Sites.Where(s => s.Id == siteId).FirstOrDefault();
        Configuration config = serverManager.GetWebConfiguration(site.Name);
        ConfigurationSection httpErrorsSection = config.GetSection("system.webServer/httpErrors");
        ConfigurationElementCollection httpErrorsCollection = httpErrorsSection.GetCollection();

        // Take a copy because existing elements can't be modified.
        httpErrorsCollectionLocal = CopyLocalErrorCollection(httpErrorsSection.GetCollection()
                                      .Where(e => e.IsLocallyStored).ToList<ConfigurationElement>());

        httpErrorsSection.RevertToParent();

        // Have to commit here because RevertToParent won't clear the collection until called.
        serverManager.CommitChanges();
        return httpErrorsCollectionLocal;
      }
    }

    private static List<Error> CopyLocalErrorCollection(List<ConfigurationElement> collection)
    {
      List<Error> errors = new List<Error>();
      foreach (var error in collection)
      {
        errors.Add(new Error()
        {
          StatusCode = (long)error["statusCode"],
          SubStatusCode = (int)error["subStatusCode"],
          ResponseMode = (ResponseMode)error["responseMode"],
          Path = (string)error["path"],
          prefixLanguageFilePath = (string)error["prefixLanguageFilePath"]
        });
      }
      return errors;
    }

    private static void AddOrUpdateError(List<Error> collection, long statusCode, int subStatusCode, 
                                             ResponseMode responseMode, string path, string prefixLanguageFilePath)
    {
      // Add or update error
      Error error = collection.Find(ce => ce.StatusCode == statusCode && ce.SubStatusCode == subStatusCode);
      if (error == null)
      {
        collection.Add(new Error()
        {
          StatusCode = statusCode,
          SubStatusCode = subStatusCode,
          ResponseMode = responseMode,
          Path = path,
          prefixLanguageFilePath = prefixLanguageFilePath
        });
      }
      else
      {
        error.ResponseMode = responseMode;
        error.Path = path;
        error.prefixLanguageFilePath = prefixLanguageFilePath;
      }
    }

    private class Error
    {
      public long StatusCode { get; set; }
      public int SubStatusCode { get; set; }
      public ResponseMode ResponseMode { get; set; }
      public string Path { get; set; }
      public string prefixLanguageFilePath { get; set; }
    }  
  }
}


回答3:

Well this is what I've found for this issue:

ServerManager serverManager = new ServerManager();
Configuration config = serverManager.GetWebConfiguration(siteName);
ConfigurationSection httpErrorsSection = config.GetSection("system.webServer/httpErrors");
ConfigurationElementCollection httpErrorsCollection = httpErrorsSection.GetCollection();
foreach (ConfigurationElement errorEle in httpErrorsCollection) {
    if (errorEle("statusCode") == statusCode && errorEle("subStatusCode") == subStatusCode) {
        errorEle.Delete();
        serverManager.CommitChanges();
        return;
    }
}

I get a list of elements as normal, then loop to find the one I want, and then call delete on the element. That's it and it works. I don't know if this existed when this question was asked, but it does now. You'll have to make sure you know that status code and substatus code.