Can't create Session in Asp MVC - asp.net-mvc

I have an ASP.NET MVC Application which is published on 2 IIS web servers (one for debug and one for release).
In this application, I use a Session variable to keep the user Login, but in the release server this Session definition didn't work, returning the error:
object reference not set to an instance of an object
I believe that it can only be IIS configurations, because the same code works fine on the debug server and in localhost, that is why I can not debug the application.
I have never made a IIS configuration before, but in my search about it, I found that there is a config for the Sessions State (see this link here), which, for default, is set as true, and if I try to set false, nothing different happens.
Here's the code:
// POST: Auth/Login/{login}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(DTOViewModel login)
{
try
{
this.Autenticar(login.Usuario, login.Password);
Session["user"] = login.Usuario; // <---- Here is the problem
return Json(new { url = Url.Action("Index", "User/Home") });
}
catch (Exception ex)
{
return Json(new { erro = ex.Message });
}
}

Solution 1:
Check the maximum worker process associated with the application pool. It should be 1.
Steps to check the maximum worker process:
Go to the application pool from left panel in IIS
Select application pool which is associated with your website.
Go to the advance setting of that application pool
Set the Process Model --> "maximum worker process" to 1
Solution 2:
Change the below setting in web.config file:
<configuration>
<system.webServer>
<modules>
<remove name="Session" />
<add name="Session" type="System.Web.SessionState.SessionStateModule"/>
</modules>
</system.webServer>
</configuration>

Related

Hangfire package in MVC on local IIS server responds with "The website declined to show this webpage" [duplicate]

I am running HangFire within my MVC web app but whenever I try to navigate to http://MyApp/hangfire, it redirects me to my app's login page as though I am not logged in.
I have not explicitly configured any requirements for authorization...e.g. I had the below in the web.config, but then took it out in attempts to get this to work.
<location path="hangfire">
<system.web>
<authorization>
<allow roles="Administrator" />
<deny users="*" />
</authorization>
</system.web>
In theory, this is what I'd want, and when I log into my main web application, I will be logged in with an Administrator role so this rule should work.
But whether I have that configured in the web.config or not, whenever I try to navigate to http://MyApp/hangfire, it redirects me to my apps login page as configured in the web.config:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="960" />
</authentication>
It does NOT do this on my local machine, just when I publish to my host. Does HangFire not recognize the authentication cookie that my main app provides when I login? I thought in general, the hangfire app doesn't require authentication, so what other configuration could be thinking that it does?
UPDATE 1:
I added the authorization filters per the hangfire docs, but the same thing happens. Here is my code in Startup.cs:
using Hangfire;
using Hangfire.Logging;
using Hangfire.Dashboard;
using Hangfire.SqlServer;
using Microsoft.Owin;
using OTIS.Web.AppCode;
using OTISScheduler.AppServ;
using Owin;
using System.Web.Security;
[assembly: OwinStartup(typeof(OTIS.Web.App_Start.Startup))]
namespace OTIS.Web.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app) {
app.UseHangfire(config => {
config.UseSqlServerStorage("DefaultConnection");
config.UseServer();
//Dashboard authorization
config.UseAuthorizationFilters(new AuthorizationFilter
{
Users = "USERA", // allow only specified users (comma delimited list)
Roles = "Account Administrator, Administrator" // allow only specified roles(comma delimited list)
});
});
LogProvider.SetCurrentLogProvider(new StubLogProviderForHangfire());
GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 0 });
var scheduleTasksInitializer = new ScheduleTasksInitializer();
scheduleTasksInitializer.ScheduleTasks();
}
}
}
UPDATE 2:
Per the more detailed instructions showing basic authentication, I also tried this...still no luck..redirects me to my app's login page.
config.UseAuthorizationFilters(
new BasicAuthAuthorizationFilter(
new BasicAuthAuthorizationFilterOptions
{
// Require secure connection for dashboard
RequireSsl = false,
SslRedirect = false,
// Case sensitive login checking
LoginCaseSensitive = true,
// Users
Users = new[]
{
new BasicAuthAuthorizationUser
{
Login = "MyLogin",
// Password as plain text
PasswordClear = "MyPwd"
}
}
}));
With the newer versions you should use IDashboardAuthorizationFilter. With the using statements, it will look like this:
using System.Web;
using Hangfire.Annotations;
using Hangfire.Dashboard;
namespace Scheduler.Hangfire
{
public class HangFireAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize([NotNull] DashboardContext context)
{
//can add some more logic here...
return HttpContext.Current.User.Identity.IsAuthenticated;
//Can use this for NetCore
return context.GetHttpContext().User.Identity.IsAuthenticated;
}
}
}
then in the configuration section:
app.UseHangfireDashboard("/jobs", new DashboardOptions()
{
Authorization = new [] {new HangFireAuthorizationFilter()}
});
Finally got it working. I created my own AuthorizationFilter class (see below).
Then I passed that to the MapHangfireDashboard method in the Startup.cs Configuration method (see below that)
public class HangFireAuthorizationFilter : IAuthorizationFilter
{
public bool Authorize(IDictionary<string, object> owinEnvironment)
{
bool boolAuthorizeCurrentUserToAccessHangFireDashboard = false;
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if(HttpContext.Current.User.IsInRole("Account Administrator"))
boolAuthorizeCurrentUserToAccessHangFireDashboard = true;
}
return boolAuthorizeCurrentUserToAccessHangFireDashboard;
}
}
To map hangfire to a custom url and specify the AuthorizationFilter to use:
public void Configuration(IAppBuilder app) {
//Get from web.config to determine to fire up hangfire scheduler or not
app.UseHangfire(config => {
config.UseSqlServerStorage("DefaultConnection");
config.UseServer();
});
//map hangfire to a url and specify the authorization filter to use to allow access
app.MapHangfireDashboard("/Admin/jobs", new[] { new HangFireAuthorizationFilter() });
}
As designed I believe.
See the docs for the dashboard.
By default Hangfire allows access to Dashboard pages only for local requests.
Strangely enough I was dealing with this the other day and one thing to be aware of is that if you are using Autofac dependency injection then you need to make sure you configure items in the correct order. Specifically Hangfire after other authentication but also, in my case, MembershipReboot before the other OAuth stuff.
Took quite a bit of trial and error.

Why is Hangfire requiring authentication to view dashboard

I am running HangFire within my MVC web app but whenever I try to navigate to http://MyApp/hangfire, it redirects me to my app's login page as though I am not logged in.
I have not explicitly configured any requirements for authorization...e.g. I had the below in the web.config, but then took it out in attempts to get this to work.
<location path="hangfire">
<system.web>
<authorization>
<allow roles="Administrator" />
<deny users="*" />
</authorization>
</system.web>
In theory, this is what I'd want, and when I log into my main web application, I will be logged in with an Administrator role so this rule should work.
But whether I have that configured in the web.config or not, whenever I try to navigate to http://MyApp/hangfire, it redirects me to my apps login page as configured in the web.config:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="960" />
</authentication>
It does NOT do this on my local machine, just when I publish to my host. Does HangFire not recognize the authentication cookie that my main app provides when I login? I thought in general, the hangfire app doesn't require authentication, so what other configuration could be thinking that it does?
UPDATE 1:
I added the authorization filters per the hangfire docs, but the same thing happens. Here is my code in Startup.cs:
using Hangfire;
using Hangfire.Logging;
using Hangfire.Dashboard;
using Hangfire.SqlServer;
using Microsoft.Owin;
using OTIS.Web.AppCode;
using OTISScheduler.AppServ;
using Owin;
using System.Web.Security;
[assembly: OwinStartup(typeof(OTIS.Web.App_Start.Startup))]
namespace OTIS.Web.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app) {
app.UseHangfire(config => {
config.UseSqlServerStorage("DefaultConnection");
config.UseServer();
//Dashboard authorization
config.UseAuthorizationFilters(new AuthorizationFilter
{
Users = "USERA", // allow only specified users (comma delimited list)
Roles = "Account Administrator, Administrator" // allow only specified roles(comma delimited list)
});
});
LogProvider.SetCurrentLogProvider(new StubLogProviderForHangfire());
GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 0 });
var scheduleTasksInitializer = new ScheduleTasksInitializer();
scheduleTasksInitializer.ScheduleTasks();
}
}
}
UPDATE 2:
Per the more detailed instructions showing basic authentication, I also tried this...still no luck..redirects me to my app's login page.
config.UseAuthorizationFilters(
new BasicAuthAuthorizationFilter(
new BasicAuthAuthorizationFilterOptions
{
// Require secure connection for dashboard
RequireSsl = false,
SslRedirect = false,
// Case sensitive login checking
LoginCaseSensitive = true,
// Users
Users = new[]
{
new BasicAuthAuthorizationUser
{
Login = "MyLogin",
// Password as plain text
PasswordClear = "MyPwd"
}
}
}));
With the newer versions you should use IDashboardAuthorizationFilter. With the using statements, it will look like this:
using System.Web;
using Hangfire.Annotations;
using Hangfire.Dashboard;
namespace Scheduler.Hangfire
{
public class HangFireAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize([NotNull] DashboardContext context)
{
//can add some more logic here...
return HttpContext.Current.User.Identity.IsAuthenticated;
//Can use this for NetCore
return context.GetHttpContext().User.Identity.IsAuthenticated;
}
}
}
then in the configuration section:
app.UseHangfireDashboard("/jobs", new DashboardOptions()
{
Authorization = new [] {new HangFireAuthorizationFilter()}
});
Finally got it working. I created my own AuthorizationFilter class (see below).
Then I passed that to the MapHangfireDashboard method in the Startup.cs Configuration method (see below that)
public class HangFireAuthorizationFilter : IAuthorizationFilter
{
public bool Authorize(IDictionary<string, object> owinEnvironment)
{
bool boolAuthorizeCurrentUserToAccessHangFireDashboard = false;
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if(HttpContext.Current.User.IsInRole("Account Administrator"))
boolAuthorizeCurrentUserToAccessHangFireDashboard = true;
}
return boolAuthorizeCurrentUserToAccessHangFireDashboard;
}
}
To map hangfire to a custom url and specify the AuthorizationFilter to use:
public void Configuration(IAppBuilder app) {
//Get from web.config to determine to fire up hangfire scheduler or not
app.UseHangfire(config => {
config.UseSqlServerStorage("DefaultConnection");
config.UseServer();
});
//map hangfire to a url and specify the authorization filter to use to allow access
app.MapHangfireDashboard("/Admin/jobs", new[] { new HangFireAuthorizationFilter() });
}
As designed I believe.
See the docs for the dashboard.
By default Hangfire allows access to Dashboard pages only for local requests.
Strangely enough I was dealing with this the other day and one thing to be aware of is that if you are using Autofac dependency injection then you need to make sure you configure items in the correct order. Specifically Hangfire after other authentication but also, in my case, MembershipReboot before the other OAuth stuff.
Took quite a bit of trial and error.

Session_start not being called on logout -> login with session state using azure redis cache

As MS announced to replace shared cache to new azure redis cache i have migrated my app to use redis cache instead of azure shared cache according to this
Now i am facing one issue where on logout -> login it would not call session_start method where my app would set values along with user object like activities (user can perform) and some other.
While i was using inproc session state or previously azure shared cache then on logout->login it would call logoutcallback() >> session_start() >> AuthorizeCore() but with redis cache it would call
logoutcallback() >> AuthorizeCore() , i could not figure out what is issue here?
Logout & login code is as below
public void Logout()
{
//redirect url
var RedirectUrl = string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Host, Url.RouteUrl("Default", new { controller = "Account", action = "LogoutCallback" }));
//logout
WSFederationAuthenticationModule authModule = FederatedAuthentication.WSFederationAuthenticationModule;
string signoutUrl = (WSFederationAuthenticationModule.GetFederationPassiveSignOutUrl(authModule.Issuer, RedirectUrl, null));
Response.Redirect(signoutUrl, true);
}
[AllowAnonymous]
public void LogoutCallback()
{
Session.Abandon();
Response.Redirect(Url.Content("~/"), true);
}
and my web.config setting as below for redis configuration for session state
<sessionState mode="Custom" customProvider="MySessionStateStore" >
<providers>
<add name="MySessionStateStore" type="Microsoft.Web.Redis.RedisSessionStateProvider" host="[my host]" accessKey="[my key]" ssl="true" operationTimeoutInMilliseconds = "1200000" connectionTimeoutInMilliseconds = "1200000"/>
</providers>
</sessionState>
Thanks.
We could not repro this issue. Can you give us a reproducible web app that we can use to investigate it further?

ASP.NET MVC 4 - Exception Handling Not Working

When an error occurs in my ASP.NET MVC 4 application, I would like to customize a view for the user depending on the type of error. For example, page not found or an exception has occurred (with some user-friendly details about the exception). I've checked other examples of how to do this on StackOverflow and other online sources, but none of the answers are working for me.
The basic [HandleError] attribute does not seem to be working in VS2012 with an MVC 4 application targeting .NET 4.5. Here is the code I have in my home controller:
[HandleError]
public ActionResult Index()
{
Response.TrySkipIisCustomErrors = true; //doesn't work with or without this
throw new NullReferenceException("Uh oh, something broke.");
}
It is just throwing an exception, and I would expect the default ~/Shared/Error.cshtml view to be returned because of the [HandleError] attribute, but all I get is an HTTP 500 Internal Server Error indicating that the page could not be displayed. I checked my web.config, and different configurations seem to be behaving weird. In the section, it currently contains:
<customErrors mode="On" />
(I've tried adding defaultRedirect and with customErrors mode="Off" as well but that didn't have any effect... neither the shared Error view or the CustomError view I have is being rendered. If I change customErrors mode to off, then I can see the exception details as expected, so it is throwing the "Uh oh, something broke" exception properly.
I've also tried adding an OnException handler to the HomeController, and although I can debug through and see that the OnException event is being raised, it doesn't make any difference:
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
filterContext.ExceptionHandled = true;
if (filterContext == null)
{
filterContext.Result = View("CustomError");
return;
}
Exception e = filterContext.Exception;
// TODO: Log the exception here
ViewData["Exception"] = e; // pass the exception to the view
filterContext.Result = View("CustomError");
}
I have also tried changing [HandleError] to specify a view, but that doesn't seem to do anything either:
[HandleError(View="CustomError")]
Any help would be much appreciated. Please let me know if you need any more details.
I also went on a seamingly endless journey of reading SO answers and assorted blog postings trying to get custom error pages to work. Below is what finally worked for me.
The first step is to use IIS Express for debugging instead of the built-in Cassini web server to "guarantee" that the debug experience will mirror the live environment.
Create a controller to handle application errors and add an action for each custom error you will handle. The action should do any logging, set the status code, and return the view.
public class ErrorsController : Controller
{
// 404
[HttpGet]
public ActionResult NotFound()
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View();
}
// I also have test actions so that I can verify it's working in production.
[HttpGet]
public ActionResult Throw404()
{
throw new HttpException((int)HttpStatusCode.NotFound, "demo");
}
}
Configure the customErrors section in web.config to redirect to your custom error actions.
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="Errors/InternalServerError">
<error statusCode="400" redirect="Errors/BadRequest" />
<error statusCode="403" redirect="Errors/Forbidden" />
<error statusCode="404" redirect="Errors/NotFound" />
<error statusCode="500" redirect="Errors/InternalServerError" />
</customErrors>
Add the httpErrors section to system.webServer and set the errorMode to Detailed in web.config. Why this works is a mystery to me, but this was the key.
<system.webServer>
<httpErrors errorMode="Detailed" />
Add a catchall route last to the defined routes to direct 404s to the custom page.
// catchall for 404s
routes.MapRoute(
"Error",
"{*url}",
new {controller = "Errors", action = "NotFound"});
You may also want to create a custom HandleErrorAttribute and register it as a global filter to log 500 errors.
These steps worked for me in both development (IIS Express) and production (IIS7) environments. You need to change customErrors mode="On" to see the effect in development.
I seem to recall that you had to call the page from a non-localhost IP address (typically another computer). And it has to be an IIS based server, not the built-in development server (so IIS or IIS Express, but then you have to configure IIS Express for external access, which is a pain).
You can in fact debug it, you have to configure your local server on your debug computer to accept external requests, then call your local server from the remote server.
I faced a similar problem and lost sometime trying to work out what was going on. So just in case any others face a similar problem here is what my problem was.
The error page was trying to use my _Layout page. Just ensure that you Error.cshtml page has
#{
Layout = null;
}
Try adding the following attribute to the customErrors tag:
defaultRedirect="Error"
This explicitly defines to which view MVC should redirect the user when the error is thrown and no view is specified by default in the attribute. This will of course only work if Error.cshtml exists in the Shared views folder.

Razor code in cshtml (error) page produce error

I throw 404 error from application.
Page for 404 error is in the /ErrorPages/Error_404.cshtml
In this file I have only HTML code and it works fine.
But if I add some razor code it throws configuration error in browser.
I add for example layout or some #Html.ActionLink(...
This is error:
Runtime Error
Description: An application error occurred on the server. The current
custom error settings for this application prevent the details of the
application error from being viewed.
Details: To enable the details of this specific error message to be
viewable on the local server machine, please create a
tag within a "web.config" configuration file located in the root
directory of the current web application. This tag
should then have its "mode" attribute set to "RemoteOnly". To enable
the details to be viewable on remote machines, please set "mode" to
"Off".
This is how I produce 404:
public ActionResult Search(string searchTerm)
{
if (city == null)
{
throw new HttpException(404, "Some description");
}
else
{
return RedirectToAction("Index", "Home", new
{...
}
}
And when there is no razor code in error page it works and if not I get message from above.
When I set in web config 'mode=Off' then I get error message:
Description: HTTP 404. The resource you are looking for (or one of its
dependencies) could have been removed, had its name changed, or is
temporarily unavailable. Please review the following URL and make
sure that it is spelled correctly.
Requested URL: /CityPage/Navigation/Search
This is value from web config
<customErrors mode="Off">
<error statusCode="404" redirect="\ErrorPages\Error_cp404.cshtml" />
</customErrors>
You shouldn't be attempting to render a .cshtml page directly. Those are Razor views. In ASP.NET MVC views are served through controller actions.
So:
<error statusCode="404" redirect="Errors/Error404" />
where you could have an ErrorsController:
public class ErrorsController: Controller
{
public ActionResult Error404()
{
return View();
}
}
You may also checkout the following answer for an alternative way to handle errors.
You might also need to add the following if you are running in IIS7+:
<system.webServer>
...
<httpErrors errorMode="Detailed" />
</system.webServer>

Resources