I have a simple personal MVC4 web app that is hosted in Windows Azure.
This web app is very minimal in use, the initial call is very slow specially when I tried to click in the morning.
I’m suspecting that the IIS is sleeping and need to wake up. I found this article and mention that this is a bug in IIS http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/8b3258e7-261c-49a0-888c-0b3e68b2af13 which required setting up in IIS but my web app is hosted in Azure, is there any way to do some sort of setting in Web.config file?
All succeeding calls are fast.
Here is my personal page. javierdelacruz.com
Thanks.
Two options:
Startup Tasks
OnStart Code
For startup tasks, see this link.
For OnStart code, try a function like this (this function does a few more things, too):
private const string _web_app_project_name = "Web";
public static void SetupDefaultEgConfiguration(int idleTimeoutInMinutes = 1440, int recycleTimeoutInMinutes = 1440, string appPoolName = "My Azure App Pool", bool enableCompression = true)
{
if (!RoleEnvironment.IsEmulated)
{
Trace.TraceWarning("Changing IIS settings upon role's OnStart. Inputs: ({0}, {1}, {2}, {3}", idleTimeoutInMinutes, recycleTimeoutInMinutes, appPoolName, enableCompression);
// Tweak IIS Settings
using (var iisManager = new ServerManager())
{
try
{
var roleSite = iisManager.Sites[RoleEnvironment.CurrentRoleInstance.Id + "_" + _web_app_project_name];
if (enableCompression)
{
//================ Enable or disable static/Dynamic compression ===================//
var config = roleSite.GetWebConfiguration();
var urlCompressionSection = config.GetSection("system.webServer/urlCompression");
urlCompressionSection["doStaticCompression"] = true;
urlCompressionSection["doDynamicCompression"] = true;
Trace.TraceWarning("Changing IIS settings to enable static and dynamic compression");
}
//================ To change ApplicationPool name ================================//
var app = roleSite.Applications.First();
app.ApplicationPoolName = appPoolName;
//================ To change ApplicationPool Recycle Timeout ================================//
var appPool = iisManager.ApplicationPools[app.ApplicationPoolName];
appPool.Recycling.PeriodicRestart.Time = new TimeSpan(0, recycleTimeoutInMinutes, 0);
//================ idletimeout ====================================================//
var defaultIdleTimeout = iisManager.ApplicationPoolDefaults.ProcessModel.IdleTimeout;
var newIdleTimeout = new TimeSpan(0, idleTimeoutInMinutes, 0);
if ((int)newIdleTimeout.TotalMinutes != (int)defaultIdleTimeout.TotalMinutes)
{
appPool.ProcessModel.IdleTimeout = newIdleTimeout;
}
// Commit the changes done to server manager.
iisManager.CommitChanges();
}
catch (Exception e)
{
Trace.TraceError("Failure when configuring IIS in Azure: " + e.ToString().Take(63000));
}
}
}
}
Source and some more details for the function I included here - there are some dependencies you'll likely need to accomplish this.
Related
I am trying to programmatically change the ApplicationPool - Identity property of the IIS server where my azure web app is hosted.
Why am I doing this?
I need to provide X.509 certificate. Implementing this certificate requires some local system data.
What have I done so far?
I use this particular code (pretty much the same from here https://stackoverflow.com/a/9341347/2900305)
private void SetAppPoolIdentity()
{
string appPoolUser = "myRDP_admin_user";
string appPoolPass = "my_super_secure_password";
Action<string> iis7fix = (appPoolName) =>
{
bool committed = false;
while (!committed)
{
try
{
using (ServerManager sm = new ServerManager())
{
var applicationPool = sm.ApplicationPools[appPoolName];
applicationPool.ProcessModel.IdentityType = ProcessModelIdentityType.SpecificUser;
//applicationPool.ProcessModel.IdentityType = ProcessModelIdentityType.LocalSystem;
applicationPool.ProcessModel.UserName = appPoolUser;
applicationPool.ProcessModel.Password = appPoolPass;
sm.CommitChanges();
committed = true;
}
}
catch (FileLoadException fle)
{
Trace.TraceError("Trying again because: " + fle.Message);
}
}
};
var appPoolNames = new ServerManager().Sites.First().Applications.Select(x => x.ApplicationPoolName).ToList();
appPoolNames.ForEach(iis7fix);
}
My problem is that my user does not have enough permissions to change the ApplicationPool - Identity to LocalSystem.
And I do not have username and password for specific user (admin or local admin) on azure hosted web app.
Any different approach or idea or workaround are welcomed.
You cannot change the App Pool identity that an Azure Web App runs under. Web Apps execute in a sandboxed environment that generally don't allow this kind of modifications.
There are ways of uploading certificates, and you may want to ask that question specifically if that is what you're trying to achieve.
I have a worker role in my hosted service.
The worker is sending e-mail daily bases.
But in the hosted service, there are 2 environment, Staging and Production.
So my worker role sends e-mail 2 times everyday.
I'd like to know how to detect if the worker is in stagning or production.
Thanks in advance.
As per my question here, you'll see that there is no fast way of doing this. Also, unless you really know what you are doing, I strongly suggest you not do this.
However, if you want to, you can use a really nice library (Azure Service Management via C#) although we did have some trouble with WCF using it.
Here's a quick sample on how to do it (note, you need to include the management certificate as a resource in your code & deploy it to Azure):
private static bool IsStaging()
{
try
{
if (!CloudEnvironment.IsAvailable)
return false;
const string certName = "AzureManagement.pfx";
const string password = "Pa$$w0rd";
// load certificate
var manifestResourceStream = typeof(ProjectContext).Assembly.GetManifestResourceStream(certName);
if (manifestResourceStream == null)
{
// should we panic?
return true;
}
var bytes = new byte[manifestResourceStream.Length];
manifestResourceStream.Read(bytes, 0, bytes.Length);
var cert = new X509Certificate2(bytes, password);
var serviceManagementChannel = Microsoft.Toolkit.WindowsAzure.ServiceManagement.ServiceManagementHelper.
CreateServiceManagementChannel("WindowsAzureServiceManagement", cert);
using (new OperationContextScope((IContextChannel)serviceManagementChannel))
{
var hostedServices =
serviceManagementChannel.ListHostedServices(WellKnownConfiguration.General.SubscriptionId);
// because we don't know the name of the hosted service, we'll do something really wasteful
// and iterate
foreach (var hostedService in hostedServices)
{
var ad =
serviceManagementChannel.GetHostedServiceWithDetails(
WellKnownConfiguration.General.SubscriptionId,
hostedService.ServiceName, true);
var deployment =
ad.Deployments.Where(
x => x.PrivateID == Zebra.Framework.Azure.CloudEnvironment.CurrentRoleInstanceId).
FirstOrDefault
();
if (deployment != null)
{
return deployment.DeploymentSlot.ToLower().Equals("staging");
}
}
}
return false;
}
catch (Exception e)
{
// if something went wrong, let's not panic
TraceManager.AzureFrameworkTraceSource.TraceData(System.Diagnostics.TraceEventType.Error, "Exception", e);
return false;
}
}
If you're using an SQL server (either Azure SQL or SQL Server hosted in VM), you could stop the Staging worker role from doing work by only allowing the public IP of the Production instance access to the database server.
Can anyone help create an Application Pool in IIS using C#?
Once this has been done, how do I assign the Application Pool to a virtual directory, again using C#?
If you're using IIS7:
To create an application pool using and set the .NET Framework version (to v2.0 in this case), do this:
using Microsoft.Web.Administration;
...
using(ServerManager serverManager = new ServerManager())
{
ApplicationPool newPool = serverManager.ApplicationPools.Add("MyNewPool");
newPool.ManagedRuntimeVersion = "v2.0";
serverManager.CommitChanges();
}
You should add a reference to Microsoft.Web.Administration.dll which can be found in:
%SYSTEMROOT%\System32\InetSrv
To assign a virtual directory to an application pool (though I think you mean an application):
using (ServerManager serverManager = new ServerManager())
{
// Find Default Website
Site site = serverManager.Sites.First(s => s.Id == 1);
Application newApp = site.Applications.Add("/MyNewApp",
#"C:\inetpub\wwwroot\mynewapp");
newApp.ApplicationPoolName = "MyNewPool";
serverManager.CommitChanges();
}
If you're using IIS6:
using (DirectoryEntry appPools =
new DirectoryEntry("IIS://localhost/W3SVC/AppPools"))
{
using (DirectoryEntry newPool = appPools.Children.Add("MyNewPool",
"IIsApplicationPool"))
{
// Just use NetworkService as pool account
newPool.Properties["AppPoolIdentityType"].Value = 2;
newPool.CommitChanges();
}
}
The following code creates an application called MyNewApp in the Default Web Site and assigns it to the application pool MyNewPool we created using the code example above:
using (DirectoryEntry siteRoot =
new DirectoryEntry(#"IIS://Localhost/W3SVC/1/root"))
{
using (DirectoryEntry newApp =
siteRoot.Children.Add("MyNewApp", "IIsWebVirtualDir"))
{
newApp.Properties["Path"].Value = #"C:\inetpub\wwwroot\mynewapp";
newApp.Properties["AccessScript"][0] = true;
newApp.Properties["AccessFlags"].Value = 513; // AccessScript | AccessRead
newApp.Properties["AuthFlags"].Value = 7;// AuthAnonymous|AuthBasic|AuthNTLM
newApp.Properties["AppIsolated"].Value = "2";
newApp.Properties["AppRoot"].Value =
newApp.Path.Replace("IIS://Localhost", "/LM");
newApp.Properties["AppPoolId"].Value = "MyNewPool";
newApp.Properties["AppFriendlyName"].Value = "MyNewApp";
newApp.CommitChanges();
}
}
I all of the above cases your code needs to be running as an administrator.
For more information see:
IIS7:
IIS 7 Configuration Reference
How to Use Microsoft.Web.Administration
IIS6:
Using System.DirectoryServices to Configure IIS
IIS Programmatic Administration Reference
IIS Metabase Properties
I believe this depends on which version of IIS you are using but check out:
http://msdn.microsoft.com/en-us/library/ms525598.aspx (example is for IIS7)
Source was from another question: Programmatically create a web site in IIS using C# and set port number
As part of starting up a WebRole on Windows Azure I would like to access files on the website being started and I would like to do this in RoleEntryPoint.OnStart(). This will for instance enable me to influence ASP.NET config before the ASP.NET AppDomain is loaded.
When running locally with Azure SDK 1.3 and VS2010 the sample code below do the trick, but the code has the stench of hack around it and it does not do the trick when deploying to Azure.
XNamespace srvDefNs = "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition";
DirectoryInfo di = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
string roleRoot = di.Parent.Parent.FullName;
XDocument roleModel = XDocument.Load(Path.Combine(roleRoot, "RoleModel.xml"));
var propertyElements = roleModel.Descendants(srvDefNs + "Property");
XElement sitePhysicalPathPropertyElement = propertyElements.Attributes("name").Where(nameAttr => nameAttr.Value == "SitePhysicalPath").Single().Parent;
string pathToWebsite = sitePhysicalPathPropertyElement.Attribute("value").Value;
How can I get the WebRole site root path from RoleEntryPoint.OnStart() in a way that work in both dev and on Azure?
This seem to work in both dev and on Windows Azure:
private IEnumerable<string> WebSiteDirectories
{
get
{
string roleRootDir = Environment.GetEnvironmentVariable("RdRoleRoot");
string appRootDir = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
XDocument roleModelDoc = XDocument.Load(Path.Combine(roleRootDir, "RoleModel.xml"));
var siteElements = roleModelDoc.Root.Element(_roleModelNs + "Sites").Elements(_roleModelNs + "Site");
return
from siteElement in siteElements
where siteElement.Attribute("name") != null
&& siteElement.Attribute("name").Value == "Web"
&& siteElement.Attribute("physicalDirectory") != null
select Path.Combine(appRootDir, siteElement.Attribute("physicalDirectory").Value);
}
}
If anyone use this to manipulate files in the ASP.NET app, you should know that the files written by RoleEntryPoint.OnStart() will have ACL settings that prevent the ASP.NET application from updating them.
If you need to write to such files from ASP.NET this code show how you can change file permissions so this is possible:
SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
IdentityReference act = sid.Translate(typeof(NTAccount));
FileSecurity sec = File.GetAccessControl(testFilePath);
sec.AddAccessRule(new FileSystemAccessRule(act, FileSystemRights.FullControl, AccessControlType.Allow));
File.SetAccessControl(testFilePath, sec);
Take a look at:
Environment.GetEnvironmentVariable("RoleRoot")
Does that give you what you're looking for?
Is there anywhere in the service runtime that would tell me if I'm currently running on 'Staging' or 'Production'? Manually modifying the config to and from production seems a bit cumbersome.
You should really not change your configurations when you're based upon if you're in Prod or Staging. Staging area is not designed to be a "QA" environment but only a holding-area before production is deployed.
When you upload a new deployment, current deployment slot where you upload your package to is destroyed and is down for 10-15minutes while upload and start of VM's is happening. If you upload straight into production, that's 15 minutes of production downtime. Thus, Staging area was invented: you upload to staging, test the stuff, and click "Swap" button and your Staging environment magically becomes Production (virtual IP swap).
Thus, your staging should really be 100% the same as your production.
What I think you're looking for is QA/testing environment? You should open up a new service for Testing environment with its own Prod/Staging. In this case, you will want to maintain multiple configuration file sets, one set per deployment environment (Production, Testing, etc.)
There are many ways to manage configuration-hell that occurs, especially with Azure that has on top of .config files, its own *.cscfg files. The way I prefer to do it with Azure project is as follows:
Setup a small Config project, create folders there that match Deployment types. Inside each folder setup sets of *.config & *.cscfg files that match to particular deployment environment: Debug, Test, Release... these are setup in Visual Studio as well , as build target types. I have a small xcopy command that occurs during every compile of the Config project that copies all the files from Build Target folder of Config project into root folder of the Config project.
Then every other project in the solution, LINKS to the .config or .cscfg file from the root folder of the Config project.
Voila, my configs magically adapt to every build configuration automatically. I also use .config transformations to manage debugging information for Release vs. non-Release build targets.
If you've read all this and still want to get at the Production vs. Staging status at runtime, then:
Get deploymentId from RoleEnvironment.DeploymentId
Then use Management API with a proper X509 certificate to get at the Azure structure of your Service and call the GetDeployments method (it's rest api but there is an abstraction library).
Hope this helps
Edit: blog post as requested about the setup of configuration strings and switching between environments # http://blog.paraleap.com/blog/post/Managing-environments-in-a-distributed-Azure-or-other-cloud-based-NET-solution
Sometimes I wish people would just answer the question.. not explain ethics or best practices...
Microsoft has posted a code sample doing exactly this here: https://code.msdn.microsoft.com/windowsazure/CSAzureDeploymentSlot-1ce0e3b5
protected void Page_Load(object sender, EventArgs e)
{
// You basic information of the Deployment of Azure application.
string deploymentId = RoleEnvironment.DeploymentId;
string subscriptionID = "<Your subscription ID>";
string thrumbnail = "<Your certificate thumbnail print>";
string hostedServiceName = "<Your hosted service name>";
string productionString = string.Format(
"https://management.core.windows.net/{0}/services/hostedservices/{1}/deploymentslots/{2}",
subscriptionID, hostedServiceName, "Production");
Uri requestUri = new Uri(productionString);
// Add client certificate.
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly);
X509Certificate2Collection collection = store.Certificates.Find(
X509FindType.FindByThumbprint, thrumbnail, false);
store.Close();
if (collection.Count != 0)
{
X509Certificate2 certificate = collection[0];
HttpWebRequest httpRequest = (HttpWebRequest)HttpWebRequest.Create(requestUri);
httpRequest.ClientCertificates.Add(certificate);
httpRequest.Headers.Add("x-ms-version", "2011-10-01");
httpRequest.KeepAlive = false;
HttpWebResponse httpResponse = httpRequest.GetResponse() as HttpWebResponse;
// Get response stream from Management API.
Stream stream = httpResponse.GetResponseStream();
string result = string.Empty;
using (StreamReader reader = new StreamReader(stream))
{
result = reader.ReadToEnd();
}
if (result == null || result.Trim() == string.Empty)
{
return;
}
XDocument document = XDocument.Parse(result);
string serverID = string.Empty;
var list = from item
in document.Descendants(XName.Get("PrivateID",
"http://schemas.microsoft.com/windowsazure"))
select item;
serverID = list.First().Value;
Response.Write("Check Production: ");
Response.Write("DeploymentID : " + deploymentId
+ " ServerID :" + serverID);
if (deploymentId.Equals(serverID))
lbStatus.Text = "Production";
else
{
// If the application not in Production slot, try to check Staging slot.
string stagingString = string.Format(
"https://management.core.windows.net/{0}/services/hostedservices/{1}/deploymentslots/{2}",
subscriptionID, hostedServiceName, "Staging");
Uri stagingUri = new Uri(stagingString);
httpRequest = (HttpWebRequest)HttpWebRequest.Create(stagingUri);
httpRequest.ClientCertificates.Add(certificate);
httpRequest.Headers.Add("x-ms-version", "2011-10-01");
httpRequest.KeepAlive = false;
httpResponse = httpRequest.GetResponse() as HttpWebResponse;
stream = httpResponse.GetResponseStream();
result = string.Empty;
using (StreamReader reader = new StreamReader(stream))
{
result = reader.ReadToEnd();
}
if (result == null || result.Trim() == string.Empty)
{
return;
}
document = XDocument.Parse(result);
serverID = string.Empty;
list = from item
in document.Descendants(XName.Get("PrivateID",
"http://schemas.microsoft.com/windowsazure"))
select item;
serverID = list.First().Value;
Response.Write(" Check Staging:");
Response.Write(" DeploymentID : " + deploymentId
+ " ServerID :" + serverID);
if (deploymentId.Equals(serverID))
{
lbStatus.Text = "Staging";
}
else
{
lbStatus.Text = "Do not find this id";
}
}
httpResponse.Close();
stream.Close();
}
}
Staging is a temporary deployment slot used mainly for no-downtime upgrades and ability to roll back an upgrade.
It is advised not to couple your system (either in code or in config) with such Azure specifics.
Since Windows Azure Management Libraries and thanks to #GuaravMantri answer to another question you can do it like this :
using System;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Azure;
using Microsoft.WindowsAzure.Management.Compute;
using Microsoft.WindowsAzure.Management.Compute.Models;
namespace Configuration
{
public class DeploymentSlotTypeHelper
{
static string subscriptionId = "<subscription-id>";
static string managementCertContents = "<Base64 Encoded Management Certificate String from Publish Setting File>";// copy-paste it
static string cloudServiceName = "<your cloud service name>"; // lowercase
static string ns = "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration";
public DeploymentSlot GetSlotType()
{
var managementCertificate = new X509Certificate2(Convert.FromBase64String(managementCertContents));
var credentials = new CertificateCloudCredentials(subscriptionId, managementCertificate);
var computeManagementClient = new ComputeManagementClient(credentials);
var response = computeManagementClient.HostedServices.GetDetailed(cloudServiceName);
return response.Deployments.FirstOrDefault(d => d.DeploymentSlot == DeploymentSlot.Production) == null ? DeploymentSlot.Staging : DeploymentSlot.Production;
}
}
}
An easy way to solve this problem is setting at your instances an key to identify which environment it is running.
1) Set at your production slot:
Set it Settings >> Application settings >> App settings
And create a key named SLOT_NAME and value "production". IMPORTANT: check Slot setting.
2) Set at your staging slot:
Set it Settings >> Application settings >> App settings
And create a key named SLOT_NAME and value "staging". IMPORTANT: check Slot setting.
Access from your application the variable and identify which environment the application is running. In Java you can access:
String slotName = System.getenv("APPSETTING_SLOT_NAME");
Here are 4 points to consider
VIP swap only makes sense when your service faces the outside world. AKA, when it exposes an API and reacts to requests.
If all your service does is pull messages from a queue and process them, then your services is proactive and VIP swap is not a good solution for you.
If your service is both reactive and proactive, you may want to reconsider your design. Perhaps split the service into 2 different services.
Eric's suggestion of modifying the cscfg files pre- and post- VIP swap is good if the proactive part of your service can take a short down time (Because you first configure both Staging and Production to not pull messages, then perform VIP Swap, and then update Production's configuration to start pulling messages).