I'm trying to programmatically add a binding to my default website however I am consistently getting a null reference exception within the Microsoft.Web.Administration dll. Originally I wanted to assign a certificate along with the binding. I was able to query the certificate that I wanted with this:
var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);
var certificate = store.Certificates.Find(X509FindType.FindByIssuerName,
"TEST_SELF_SIGNED", true)
.OfType<X509Certificate>().FirstOrDefault();
This properly gave me the certificate I wanted, it was non-null and had the information I expected.
Site site = GetSite("Default Web Site");
var binding = site.Bindings.Add("*:443", certificate.GetCertHash(), "https");
Given that none of my variables or any other items in the sample code are null (including GetCertHash which returns a 20 byte array) I'm confused as to why I'm getting a null here. I even tried the following overload:
site.Bindings.Add("*:443", "https");
And I still get the same null ref stack:
System.NullReferenceException was unhandled Message=Object reference not set to an instance of an object. Source=Microsoft.Web.Administration StackTrace: at Microsoft.Web.Administration.Configuration.SetDirty() at Microsoft.Web.Administration.ConfigurationElement.SetDirty() at Microsoft.Web.Administration.ConfigurationElement.SetAttributeValue(String attributeName, Object value) at Microsoft.Web.Administration.Binding.SetBindingProperty(String attributeName, String value) at Microsoft.Web.Administration.BindingCollection.Add(String bindingInformation, Byte[] certificateHash, String certificateStoreName) at TestApp.Program.Main(String[] args) in C:\Projects\Cube\trunk\src\AutoUpdate\TestApp\Program.cs:line 33 at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException:
Here is a full test-app that demonstartes the issue, along with the selfssl command line arguments I used to generate the sample certificate:
selfssl.exe /T /N:CN=TEST_SELF_SIGNED /K:512 /V:9999 /Q
class Program
{
static void Main(string[] args)
{
using (ServerManager manager = new ServerManager())
{
var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);
var certificate = store.Certificates.Find(X509FindType.FindByIssuerName, "TEST_SELF_SIGNED", true).OfType<X509Certificate>().FirstOrDefault();
Site site = GetSite("Default Web Site");
site.Bindings.Add("*:443", certificate.GetCertHash(), store.Name);
store.Close();
manager.CommitChanges();
}
}
public static Site GetSite(string siteName)
{
using (var serverManager = new ServerManager())
{
return serverManager.Sites.Where(p => p.Name.ToLower() == siteName.ToLower()).FirstOrDefault();
}
}
}
Just to cover my bases, Iis is installed and manually assigning the certificate works just fine.
So I found the answer by decompiling the Microsoft.Web.Administration dll and poking through the stack. It turns out that if you get a Site with a helper function it doesn't set the internal ServerManager property on the site.
The function the dll that caused the issue was this in Microsoft.Web.Administration::Configuration
internal void SetDirty()
{
if (this._hasBeenCommitted || this._configurationManager.Owner.ReadOnly)
throw new InvalidOperationException(Resources.ObjectHasBeenCommited);
this._isDirty = true;
}
The only thing that could have been null here was either _configurationManager
or _configurationManager.Owner
. I checked what Owner
was and it was a ServerManager
which tipped me off that I should probably query the Site
from within a using block of server manager. Once I did that the null ref went away and everything worked. It's unfortunate that they aren't checking for null's but maybe the assumption is nobody would ever act on a site object without the server manager context.
Anyways, here is the updated code:
class Program
{
static void Main(string[] args)
{
using (var serverManager = new ServerManager())
{
var selfSignedCnName = "TEST_SELF_SIGNED";
var websiteName = "Default Web Site";
var site = serverManager.Sites.Where(p => p.Name.ToLower() == websiteName.ToLower()).FirstOrDefault();
var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);
var certificate = store.Certificates.Find(X509FindType.FindByIssuerName, selfSignedCnName, true).OfType<X509Certificate>().FirstOrDefault();
site.Bindings.Add("*:443:", certificate.GetCertHash(), store.Name);
store.Close();
serverManager.CommitChanges();
}
}
}
It's also clear from my initial post that wrapping the entire code block in a server manager doesn't mean anything, they aren't cascaded. You have to act on the site from the server manager it came from.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With