I have a web application hosted on IIS 7 running on windows server 2008 R2.Users of my application where able to login the application with default session timeout of 20 minutes which is configured in web.config file and session settings on IIS 7.
Users authentication is done by active directory domain services running on another server, that i implemented using this code.
code:
public bool CheckUserInActiveDirectory(string userName)
{
try
{
string filter = "(&(objectCategory=person)(objectClass=user)(SAMAccountName=" + userName + "))";
string[] propertiesToLoad = new string[2] { "name", "PwdLastSet" };
DirectoryEntry root = new DirectoryEntry(activeDirectoryAddress);
root.AuthenticationType = AuthenticationTypes.None;
root.Username = activeDirectoryName + activeDirectoryDefaultUser;
root.Password = activeDirectoryDefaultPass;
DirectorySearcher searcher = new DirectorySearcher(root, filter, propertiesToLoad);
int count = searcher.FindAll().Count;
if (count >= 1)
return true;
else
return false;
}
catch (Exception ex)
{
throw;
}
}
since my application server (with windows server 2008 R2) is joined to Domain Group, Users session is null, less than 20 minutes and the session time is not equal for other users, and is changed every time for each user.
Finally the user is redirected to login page.Is there any body who knows the reason and guide me how to resolve the issue.
code :
protected void Page_Load(object sender, EventArgs e)
{
if (Session["UserSession"] == null)
Response.Redirect("LoginPage.aspx");
}
Related
I have an Asp.Net MVC 5 web application which has an error handling mechanism for log error in DB and showing the errorId to the user who raised that error in a separate form which is called ErrorPage. When an error occurs in the web app I store that errorId in session and I read it from the session in ErrorPage to showing that errorId to the user who faced this error in order to be able for backup operations. The web server of this web application is processing requests with only one worker process currently, so all generated sessions are valid and accessible in the whole of the web app.
I am going to increase the number of worker processes from 1 to 4 for this web app but I have some problem with my web app. Also in IIS, I set the session state mode to In Process mode because of in the web app I used session in many cases and I can't set it to SQL Server mode because of it will increase performance overhead.
The problem is where a request goes in worker process A (for example) and a session will generate for this request in the worker process A and suppose this request encounters an error in web application, I will redirect the user to the ErrorPage and it is possible this new request (redirecting user to ErrorPage's action in ErrorController) goes in another worker process B (for example). But in the worker process B, I can't access that session which is generated for the first request because of that sessions is defined at the worker process level and they are valid only in that worker process.
So after a lot of search for this, I decided to save session info in DB instead of Ram and load it from DB when I need that info. But I have no idea about saving this info in DB with which key ID?
Imagine this scenario to find out my real problem easier:
let's have:
WorkerProcessId1 = W1;
WorkerProcessId2 = W2;
SessionId1 = S1;
SessionId2 = S2;
RequestId1 = R1;
RequestId2 = R2;
and the scenario:
R1 comes to web server
==> web server passes R1 to W1
==> W1 generates S1 for R1
==> R1 faces an error
==> for the user who sends R1 (it is possible the user has not logged in yet so I don't know the userId), I will save the error in DB using the combination of S1 and userId in a specific pattern as a unique identifier in Error table in DB
==> the user will redirect to ErrorPage with another request R2
==> web server passes R2 to W2
==> W2 generates S2 for R2
==> after the redirect is done, in the ErrorPage I need the errorId of that error which I save it to DB, for showing it to the user for backup operations
==> I don't know which error belongs to this user and which error should be load from DB????
If this is not possible to do that, is there any way to have a shared identifier across all worker processes of the web server?
Edit:
In this edit, I will explain where and how I used from the session in my ErrorHandling mechanism. At the end of the target line there is a commented phrase where it is written "Here I am using session":
namespace MyDomain.UI.Infrastructure.Attributes
{
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public CustomHandleErrorAttribute()
{
}
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
{
return;
}
if (!ExceptionType.IsInstanceOfType(filterContext.Exception))
{
return;
}
var errorid = 0;
try
{
errorid = SaveErrorToDatabase(filterContext);
}
catch (Exception e)
{
//Console.WriteLine(e);
//throw;
}
// if the request is AJAX return JSON else view.
if (filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new
{
error = true,
message = "Error Message....",
errorid,
}
};
}
else
{
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
filterContext.Controller.TempData.Clear();
filterContext.Controller.TempData.Add("ErrorCode", errorid);//Here I am using session
filterContext.Result = new ViewResult
{
ViewName = View,
MasterName = Master,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
}
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
private int SaveErrorToDatabase(ExceptionContext exception)
{
MyDomainDBContext dbContext = new MyDomainDBContext();
var browserType = exception.HttpContext.Request.Browser.Capabilities["type"];
var error = new Error
{
ErrorURL = exception.HttpContext.Request.Url.ToString(),
ExceptionType = exception.Exception.GetType().Name,
IsGlobalError = false,
Message = exception.Exception.Message,
StackTrace = exception.Exception.StackTrace,
ThrownTime = DateTime.Now,
UserIP = IPAddress.Parse(exception.HttpContext.Request.UserHostAddress).ToString(),
BrowserName = browserType.ToString() + "," +
GetUserPlatform(exception.HttpContext.Request)
};
AddRequestDetails(exception.Exception, exception.HttpContext.Request, error);
if (exception.Exception.InnerException != null)
{
error.Message += "\n Inner Excpetion : \n " + exception.Exception.InnerException.Message;
if (exception.Exception.InnerException.InnerException != null)
{
error.Message += "\n \t Inner Excpetion : \n " + exception.Exception.InnerException.InnerException.Message;
}
}
if (exception.HttpContext.User.Identity.IsAuthenticated)
{
error.UserID = exception.HttpContext.User.Identity.GetUserId<int>();
}
dbContext.Errors.Add(error);
dbContext.SaveChanges();
return error.ErrorID;
}
private void AddRequestDetails(Exception exception, HttpRequestBase request, Error err)
{
if (exception.GetType().Name == "HttpAntiForgeryException" && exception.Message == "The anti-forgery cookie token and form field token do not match.")
{
if (request.Form != null)
{
if (request.Cookies["__RequestVerificationToken"] != null)
{
err.RequestDetails = "Form : " + request.Form["__RequestVerificationToken"] +
" \n Cookie : " + request.Cookies["__RequestVerificationToken"].Value;
}
else
{
err.RequestDetails = "Does not have cookie for forgery";
}
}
}
}
private String GetUserPlatform(HttpRequestBase request)
{
var ua = request.UserAgent;
if (ua.Contains("Android"))
return $"Android";
if (ua.Contains("iPad"))
return $"iPad OS";
if (ua.Contains("iPhone"))
return $"iPhone OS";
if (ua.Contains("Linux") && ua.Contains("KFAPWI"))
return "Kindle Fire";
if (ua.Contains("RIM Tablet") || (ua.Contains("BB") && ua.Contains("Mobile")))
return "Black Berry";
if (ua.Contains("Windows Phone"))
return $"Windows Phone";
if (ua.Contains("Mac OS"))
return "Mac OS";
if (ua.Contains("Windows NT 5.1") || ua.Contains("Windows NT 5.2"))
return "Windows XP";
if (ua.Contains("Windows NT 6.0"))
return "Windows Vista";
if (ua.Contains("Windows NT 6.1"))
return "Windows 7";
if (ua.Contains("Windows NT 6.2"))
return "Windows 8";
if (ua.Contains("Windows NT 6.3"))
return "Windows 8.1";
if (ua.Contains("Windows NT 10"))
return "Windows 10";
//fallback to basic platform:
return request.Browser.Platform + (ua.Contains("Mobile") ? " Mobile " : "");
}
}
public class IgnoreErrorPropertiesResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (new[]{
"InputStream",
"Filter",
"Length",
"Position",
"ReadTimeout",
"WriteTimeout",
"LastActivityDate",
"LastUpdatedDate",
"Session"
}.Contains(property.PropertyName))
{
property.Ignored = true;
}
return property;
}
}
}
As you can see I filled TempData which will be stored in the session for passing errorId by ErrorCode key to ErrorPage for showing to the user.
I found a temporary solution for passing errorId to ErrorPage by creating a new class which is inherited from HandleErrorInfo with below structure, and using this errorId in ErrorPage:
public class HandleErrorInfoExtension : HandleErrorInfo
{
public HandleErrorInfoExtension(Exception exception, string controllerName, string actionName, int errorId) : base(exception, controllerName, actionName)
{
ErrorId = errorId;
}
public int ErrorId { get; set; }
}
But I don't accept my own answer because of that still I am looking for a real solution to resolve the main problem of this question which is being able to share a data (or data structure) between all worker processes of the application. You should know I used session in some other places of my application that some of these places are vital (like a payment module) so I don't find the main solution for removing the using of the session (except using DB data storing because of performance overhead) yet. So I ask the community of developers of the StackOverflow.com to help me solve this problem.
Thanks to all of you dear colleagues.
I have a MVC Web Application makes use of Windows Authentication and Exchange Web Services. While in development, this worked great, since the application pool in IIS on my development machine is set to run under my windows user and the Exchange Server is on the same domain.
On the web server, though, all our applications are set to run under a system user that has access to all the database servers etc. The database connection uses Integrated Security, so I cannot impersonate a user over an application level.
I've been trying to impersonate the current windows user through the code as follows:
public abstract class ExchangeServiceImpersonator
{
private static WindowsImpersonationContext _ctx;
public Task<string> CreateMeetingAsync(string from, List<string> to, string subject, string body, string location, DateTime begin, DateTime end)
{
var tcs = new TaskCompletionSource<string>();
EnableImpersonation();
try
{
tcs.TrySetResult(CreateMeetingImpersonated(from, to, subject, body, location, begin, end));
}
catch(Exception e)
{
tcs.TrySetException(e);
}
finally
{
DisableImpersonation();
}
return tcs.Task;
}
public abstract string CreateMeetingImpersonated(string from, List<string> to, string subject, string body, string location, DateTime begin, DateTime end);
private static void EnableImpersonation()
{
WindowsIdentity winId = (WindowsIdentity)HttpContext.Current.User.Identity;
_ctx = winId.Impersonate();
}
private static void DisableImpersonation()
{
if (_ctx != null)
_ctx.Undo();
}
}
Then, the class that implements the abstract methods:
public class ExchangeServiceExtensionsBase : ExchangeServiceImpersonator
{
private ExchangeService _service;
public ExchangeService Service
{
get
{
if (this._service == null)
{
this._service = new ExchangeService(ExchangeVersion.Exchange2013);
this._service.Url = new Uri(WebConfigurationManager.AppSettings["ExchangeServer"]);
this._service.UseDefaultCredentials = true;
}
return this._service;
}
set { return; }
}
public override string CreateMeetingImpersonated(string from, List<string> to, string subject, string body, string location, DateTime begin, DateTime end)
{
//this.Service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, from);
Appointment meeting = new Appointment(Service);
string meetingID = Guid.NewGuid().ToString();
meeting.Subject = subject;
meeting.Body = "<span style=\"font-family:'Century Gothic'\" >" + body.Replace(Environment.NewLine, "<br/>") + "<br/><br/>" +
"<span style=\"color: white;\">Meeting Identifier: " + meetingID + "</span></span><br/><br/>";
meeting.Body.BodyType = BodyType.HTML;
meeting.Start = begin;
meeting.End = end;
meeting.Location = location;
meeting.ReminderMinutesBeforeStart = 60;
foreach (string attendee in to)
{
meeting.RequiredAttendees.Add(attendee);
}
meeting.Save(SendInvitationsMode.SendToAllAndSaveCopy);
return meetingID;
}
}
Then, the methods are accessed as follows:
public static class ExchangeServiceExtensions
{
public static async Task<string> CreateMeetingAsync(string from, List<string> to, string subject, string body, string location, DateTime begin, DateTime end)
{
ExchangeServiceImpersonator serviceImpersonator = new ExchangeServiceExtensionsBase();
return await serviceImpersonator.CreateMeetingAsync(from, to, subject, body, location, begin, end);
}
}
This still works on my local dev machine, but no matter what I do, the user accessing from the server keeps getting an access denied from the exchange server:
The request failed. The remote server returned an error: (401) Unauthorized.
I've tried leaving it on default credentials:
this._service.UseDefaultCredentials = true;
And attempting to manually set the credentials to the current (supposedly impersonated) user:
this._service.Credentials = new WebCredentials(CredentialCache.DefaultNetworkCredentials);
Also, I've tried using the Exchange ImpersonatedUserId object using the email address:
this._service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, from);
which returns the following exception:
The account does not have permission to impersonate the requested user.
By default and as a security measure, Windows will prevent you from delegating your credentials from the web server to Exchange. This means you cannot impersonate the user accessing your web site.
This is known as the "server double hop" scenario. The first "hop" is from the user's machine to the web server, and the second "hop" is from the web server to the Exchange server (Google will give you lots of hits on server double hop).
This is a good thing because it will prevent any hackers from moving around your servers.
The reason it is working on your development machine is that there is only one "hop" from your local web server to the Exchange server.
To solve it you need to allow the web server to delegate the credentials to the Exchange server. This is called Kerberos delegation and must be set up by your system administrator somehow in the Active Directory (which is beyond my knowledge).
I tried to change the AD object setting to Trust this computer for delegation.. (you need AD admin rights) but that didn't solve the problem.
My breakthrough was to set the Identity of the Application Pool (Advanced Settings...) to NetworkService. It worked also with LocalService and LocalSystem, but be careful because they have elevated rights.
What surprised me, that it didn't work with Custom account, when I entered the AD admin account that in reality got all the rights for the exchange system.
general infos about my application:
ASP.CORE 2.1 webservice
Windows Server 2016
IIS 10.0.x
internal corporate network
We are using Crystal Reports 2011 & SQL Server 2008 (Windows 7 64 bit). Whenever I try to deploy the crystal reports in the IIS it is always prompting enter the database login information. I have tried the below options
Set the login information in the code
Set IIS app pool to LocalService
Nothing works. Here is the code
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
ConfigureCrystalReports();
}
else
{
ReportDocument doc = Session["Report"] as ReportDocument;
SetLogon(doc);
CrystalReportViewer1.ReportSource = doc;
CrystalReportViewer1.RefreshReport();
}
}
private void ConfigureCrystalReports()
{
ReportDocument reportDoc;
if (!IsPostBack)
{
reportDoc = new ReportDocument();
reportDoc.Load(Server.MapPath("~/Sample.rpt"));
Session.Add("Report", reportDoc);
}
else
{
reportDoc = Session["Report"] as ReportDocument;
}
SetLogon(reportDoc);
CrystalReportViewer1.ReportSource = reportDoc;
CrystalReportViewer1.RefreshReport();
}
private void SetLogon(ReportDocument reportDoc)
{
var connectionInfo1 = new ConnectionInfo()
{
ServerName = #"ODBCDSN",
DatabaseName = "hrdw_old",
IntegratedSecurity = true
};
SetDBLogonForReport(connectionInfo1, reportDoc);
}
private void SetDBLogonForReport(ConnectionInfo connectionInfo1, ReportDocument reportDocument)
{
Tables tables = reportDocument.Database.Tables;
foreach (CrystalDecisions.CrystalReports.Engine.Table table in tables)
{
TableLogOnInfo tableLogonInfo = table.LogOnInfo;
tableLogonInfo.ConnectionInfo = connectionInfo1;
tableLogonInfo.ConnectionInfo.Type = ConnectionInfoType.SQL;
table.ApplyLogOnInfo(tableLogonInfo);
}
reportDocument.SetDatabaseLogon(connectionInfo1.UserID, connectionInfo1.Password);
}
}
Steps tried in IIS:
Application pool: ASP.NET 4.0 Default app pool
Also enabled Windows Authentication in IIS & disabled anonymous authentication.
Tried anonymous authentication only.
SQL Server has both windows and sql server authentication.
I will not be able to use dataset since the crystal reports will be developed by someone using command objects. It works perfectly good in the Visual studio 2010 environment. But doesnt work in IIS.
Am I missing something basic? Any help is appreciated.
Thanks
Shankar.
Here is how this was solved. It was a bit of workaround
CrystalDecisions.ReportAppServer.ClientDoc.ISCDReportClientDocument rptClientDoc = doc.ReportClientDocument;
CrystalDecisions.ReportAppServer.ClientDoc.ISCDReportClientDocument rcd = rptClientDoc;
string server = #"<SERVER>";
string db = "<DATABASE>";
string user = "<USER>";
string pass = "PASSWORD";
rptClientDoc.DatabaseController.LogonEx(server, db, user, pass);
//Create the logon propertybag for the connection we wish to use
CrystalDecisions.ReportAppServer.DataDefModel.PropertyBag logonDetails = new CrystalDecisions.ReportAppServer.DataDefModel.PropertyBag();
logonDetails.Add("Auto Translate", -1);
logonDetails.Add("Connect Timeout", 15);
logonDetails.Add("Data Source", server);
logonDetails.Add("General Timeout", 0);
logonDetails.Add("Initial Catalog", db);
logonDetails.Add("Integrated Security", "false");
logonDetails.Add("Locale Identifier", 1033);
logonDetails.Add("OLE DB Services", -5);
logonDetails.Add("Provider", "SQLOLEDB");
logonDetails.Add("Use Encryption for Data", 0);
//Create the QE (query engine) propertybag with the provider details and logon property bag.
CrystalDecisions.ReportAppServer.DataDefModel.PropertyBag QE_Details = new CrystalDecisions.ReportAppServer.DataDefModel.PropertyBag();
QE_Details.Add("Database DLL", "crdb_ado.dll");
QE_Details.Add("QE_DatabaseName", db);
QE_Details.Add("QE_DatabaseType", "OLE DB (ADO)");
QE_Details.Add("QE_LogonProperties", logonDetails);
QE_Details.Add("QE_ServerDescription", server);
QE_Details.Add("QE_SQLDB", "True");
QE_Details.Add("SSO Enabled", "False");
CrystalDecisions.ReportAppServer.DataDefModel.ConnectionInfo newConnInfo = new CrystalDecisions.ReportAppServer.DataDefModel.ConnectionInfo();
CrystalDecisions.ReportAppServer.DataDefModel.ConnectionInfo oldConnInfo;
CrystalDecisions.ReportAppServer.DataDefModel.ConnectionInfos oldConnInfos;
oldConnInfos = rcd.DatabaseController.GetConnectionInfos(null);
for (int I = 0; I < oldConnInfos.Count; I++)
{
oldConnInfo = oldConnInfos[I];
newConnInfo.Attributes = QE_Details;
newConnInfo.Kind = CrystalDecisions.ReportAppServer.DataDefModel.CrConnectionInfoKindEnum.crConnectionInfoKindCRQE;
try
{
rcd.DatabaseController.ReplaceConnection(oldConnInfo, newConnInfo, null, CrystalDecisions.ReportAppServer.DataDefModel.CrDBOptionsEnum.crDBOptionDoNotVerifyDB);
}
catch (Exception ex)
{
Label1.Text = ex.Message;
return;
}
}
doc.SetDatabaseLogon(user, pass);
To ensure that there is no login prompt at all at first I had to login to the controller and then set all the login credentials. Ensure that login credentials are reused during post back as well. After this is done, login using the reportdocument as well. This solves the issue of login prompt coming again and again.
Thanks
Shankar
We have very weird problem with our web site. When we run our site for the first time in the day (it is not first time after deployment) it runs very slow and take 5 minutes to load the page (any browser) even if no body is connected to that site at that moment. But, once we start using it, opens various pages, it runs like a charm - very fast (not more than 4 seconds) I mean to say and even if single or multiple users are connected to it. In clear terms, if site remains idle, site performs badly for the first time but once we start using it, it runs in usual. We are using following to build the site:
MVC 4
Dot net framework 4.5
Database: SQLAnywhere and SQL Server 2008 (we have tried to use both the database to resolve the issue but no success)
Entity Framework 5.0 using Web API model and we are using jQuery call to have data on page and showing them on DataTable grid (http://www.datatables.net/)
Hosted on IIS 7.5
Note: This site was working correctly few days back but we don't know what went wrong (in code or hosting settings etc.) during last few deployments but it works very slow. We have tried with everything but now we are in need of your expert guidance.
Many thanks in advance.
My code in global.asax.cs is:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//ConfigureApi(GlobalConfiguration.Configuration);
GlobalConfiguration.Configuration.Filters.Add(new ModelValidationFilterAttribute());
FilterConfig.RegisterHttpFilters(GlobalConfiguration.Configuration.Filters);
BundleTable.EnableOptimizations = true;
}
protected void Session_Start(object src, EventArgs e)
{
SessionHelper.EnterPriceID = 1;
SessionHelper.CompanyID = 1;
SessionHelper.RoomID = 1;
SessionHelper.UserID = 1;
SessionHelper.RoomName = "Room1";
SessionHelper.UserName = "Admin";
SessionHelper.CompanyResourceFolder = SessionHelper.EnterPriceID.ToString() + "_" + SessionHelper.CompanyID.ToString();
eTurns.DTO.Resources.ResourceHelper.ResourceDirectoryPath = HttpContext.Current.Server.MapPath(#"\Resources\" + SessionHelper.CompanyResourceFolder) + #"\";
eTurns.DTO.Resources.ResourceHelper.ResourceBaseClassPath = eTurns.DTO.Resources.ResourceHelper.ResourceDirectoryPath.Replace(#"\", ".");
System.Globalization.CultureInfo c = new System.Globalization.CultureInfo("en-US");
Thread.CurrentThread.CurrentUICulture = c;
Thread.CurrentThread.CurrentCulture = c;
Session["CurrentCult"] = c;
}
public void Application_AcquireRequestState(object sender, EventArgs e)
{
if (HttpContext.Current.Session != null && Session["CurrentCult"] != null)
{
string currentCulture = Convert.ToString(Session["CurrentCult"]);
if (String.Compare(currentCulture, System.Threading.Thread.CurrentThread.CurrentCulture.ToString(), StringComparison.OrdinalIgnoreCase) != 0)
{
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
try
{
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture(currentCulture);
}
catch
{
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-us");
}
}
}
}
Increase your IDEAL TIMEOUT 20 M to 900 M (as per your convenience)....
I hope this works.....
We're dynamically loading assemblies at startup and adding them as a reference:
BuildManager.AddReferencedAssembly(assembly);
The application supports installing new plugins at runtime. Following an install/uninstall action we are restarting the web application. I've tried:
HostingEnvironment.InitiateShutdown();
and
System.Web.HttpRuntime.UnloadAppDomain();
However, the new version of a plugin is not loaded - I believe this is due to how ASP.NET is aggressively caching referenced assemblies - especially ASP.NET MVC controllers.
In production this shouldn't be a problem since the plugin assembly version would be incremented each time. However, in development this is more of an issue since we don't wish to change the version number every time we make a slight change to a plugin.
How can we force the clearing of temp asp.net files, either programatically or using a post build event?
One solution is to "touch" global.asax but this seems a bit hacky to me.
I've used following piece of code to reset the application pool on demand. (Just connect this to a Controller Action).
Note : Since it's the application pool, you might want to check the impact to any other apps running on the same app pool.
public class IisManager
{
public static string GetCurrentApplicationPoolId()
{
// Application is not hosted on IIS
if (!AppDomain.CurrentDomain.FriendlyName.StartsWith("/LM/"))
return string.Empty;
// Application hosted on IIS that doesn't support App Pools, like 5.1
else if (!DirectoryEntry.Exists("IIS://Localhost/W3SVC/AppPools"))
return string.Empty;
string virtualDirPath = AppDomain.CurrentDomain.FriendlyName;
virtualDirPath = virtualDirPath.Substring(4);
int index = virtualDirPath.Length + 1;
index = virtualDirPath.LastIndexOf("-", index - 1, index - 1);
index = virtualDirPath.LastIndexOf("-", index - 1, index - 1);
virtualDirPath = "IIS://localhost/" + virtualDirPath.Remove(index);
var virtualDirEntry = new DirectoryEntry(virtualDirPath);
return virtualDirEntry.Properties["AppPoolId"].Value.ToString();
}
public static void RecycleApplicationPool(string appPoolId)
{
string appPoolPath = "IIS://localhost/W3SVC/AppPools/" + appPoolId;
var appPoolEntry = new DirectoryEntry(appPoolPath);
appPoolEntry.Invoke("Recycle");
}
public static void RecycleApplicationPool(string appPoolId, string username, string password)
{
string appPoolPath = "IIS://localhost/W3SVC/AppPools/" + appPoolId;
var appPoolEntry = new DirectoryEntry(appPoolPath, username, password);
appPoolEntry.Invoke("Recycle");
}
}
The overridden method is to cater for instances where you want to explicitly pass a user with Admin rights on the machine/server which hosts IIS instance.
And the controller action could be something like;
public string ResetAppPool()
{
var appPoolId = IisManager.GetCurrentApplicationPoolId();
if (appPoolId.Equals(string.Empty))
return "Application is not running inside an App Pool"; //May be not IIS 6 onwards
try
{
IisManager.RecycleApplicationPool(appPoolId); //Can only be used by Admin users
return string.Format("App pool {0} recycled successfully", appPoolId);
}
catch (Exception ex)
{
Logger.Error("Failed to recycle app pool : " + ex.StackTrace);
return string.Format("App pool {0} recycle failed", appPoolId);
}
}