I am implementing custom errors in my MVC3 app, its switched on in the web.config:
<customErrors mode="On">
<error statusCode="403" redirect="/Errors/Http403" />
<error statusCode="500" redirect="/Errors/Http500" />
</customErrors>
My controller is very simple, with corresponding correctly named views:
public class ErrorsController : Controller
{
public ActionResult Http403()
{
return View("Http403");
}
public ActionResult Http500()
{
return View("Http500");
}
}
To test, I am throwing exceptions in another controller:
public class ThrowingController : Controller
{
public ActionResult NotAuthorised()
{
throw new HttpException(403, "");
}
public ActionResult ServerError()
{
throw new HttpException(500, "");
}
}
The 403 works - I get redirected to my custom "/Errors/Http403".
The 500 does not work - I instead get redirected to the default error page in the shared folder.
Any ideas?
I've got 500 errors up and running by using the httpErrors in addition to the standard customErros config:
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="403" subStatusCode="-1" />
<error statusCode="403" path="/Errors/Http403" responseMode="ExecuteURL" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="500" path="/Errors/Http500" responseMode="ExecuteURL" />
</httpErrors>
</system.webServer>
And removing this line from global.asax
GlobalFilters.Filters.Add(new HandleErrorAttribute());
Its not perfect however as I'm trying to retrieve the last error which is always null.
Server.GetLastError()
See https://stackoverflow.com/a/7499406/1048369 for the most comprehensive piece on custom errors in MVC3 I have found which was of great help.
I've got the same problem, I catch the Exception directly in Global.asax in that case:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
var code = httpException == null ? 500 : httpException.GetHttpCode();
// Log the exception.
if (code == 500)
logError.Error(exception);
Server.ClearError();
Context.Items["error"] = code;
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("action", "Index");
routeData.Values.Add("code", code);
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
That redirects to my custom Error 500: /Error/Index?code=500
Related
My API has a ReportsController set up like so:
using System.Web.Http.Cors;
using Telerik.Reporting.Cache.File;
using Telerik.Reporting.Services;
using Telerik.Reporting.Services.WebApi;
namespace API.Controllers
{
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class ReportsController : ReportsControllerBase
{
static ReportServiceConfiguration configurationInstance;
static ReportsController()
{
configurationInstance = new ReportServiceConfiguration
{
HostAppId = "Html5App",
Storage = new FileStorage(),
ReportResolver = new ReportTypeResolver(),
// ReportSharingTimeout = 0,
// ClientSessionTimeout = 15,
};
}
public ReportsController()
{
//Initialize the service configuration
this.ReportServiceConfiguration = configurationInstance;
}
}
}
My App_Start\WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.EnableCors();
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
My Global.asax.cs:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
ReportsControllerConfiguration.RegisterRoutes(GlobalConfiguration.Configuration);
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
My web.config has the recommended binding redirects:
<dependentAssembly>
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.6.0" newVersion="5.2.6.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.6.0" newVersion="5.2.6.0" />
</dependentAssembly>
As far as I can tell, everything is set up correctly, I can call api/reports/formats and see the correct data. When I try to load this report, I get an error.
$("#reportViewer1").telerik_ReportViewer({
serviceUrl: "http://dev-api/api/reports",
reportSource: {
report: "Logic.Reports.Report1, Logic",
parameters: reportParam
},
});
It displays:
"Error loading the report viewer's templates. (Template = http://dev-api/api/reports/resources/templates/telerikReportViewerTemplate-html)."
on the page, and displays
Failed to load http://dev-api/api/reports/resources/templates/telerikReportViewerTemplate-html: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:64634' is therefore not allowed access.
in the Chrome console. I cannot figure out what I am missing.
You need to Allow CORS in your WEB API See this doc :Enable CORS
Manage NuGet Package by installing Microsoft ASP.NET CORs
Add Following lines of code in web.Config under System.WebSe
Add Following Lines of code in your web.config in system.WebServer section
.
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
<add name="Access-Control-Allow-Credentials" value="true" />
</customHeaders>
</httpProtocol>
Add [EnableCors(origins: "", headers: "", methods: "*")] in your Controller
I am developing a ASPN.NET WEB API 1(with .NET framework 4.0) application with AngularJS, and I am using session to authenticate the users(I know it should be stateless, but for legacy purpose I am using session). In my application, in every request I make to my WEB API it creates a new session, even when I set values to my session.
The sessions is allowed in my appication through this code in Global.asax:
protected void Application_BeginRequest(object sender, EventArgs e)
{
string origins = ConfigurationManager.AppSettings["cors-origins"];
bool hasSlash = origins.Substring(origins.Length - 1, 1) == "/";
if (hasSlash)
origins = origins.Substring(0, origins.Length - 1);
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", origins);
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers",
"Content-Type, Accept");
HttpContext.Current.Response.End();
}
}
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Then I set values to my session in my controller:
public HttpResponseMessage Post(LoginViewModel model)
{
// SOME BUSINESS LOGIC HERE...
FormsAuthentication.SetAuthCookie(model.User, false);
HttpContext.Current.Session["usuario"] = model.User;
return Request.CreateResponse(HttpStatusCode.Accepted, "User successfylly logged in!");
}
But when I do another request to my application to access another method in controller, it throws me an error because session is null, like in this method:
public HttpResponseMessage Get()
{
var userName = HttpContext.Current.Session["usuario"];
return Request.CreateResponse(HttpStatusCode.Accepted, userName);
}
In my web.config, session is configured like this:
<sessionState mode="InProc" customProvider="DefaultSessionProvider" >
<providers>
<add name="DefaultSessionProvider" type="System.Web.Providers.DefaultSessionStateProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" />
</providers>
</sessionState>
PS: It does not work on Chrome, but on IE it works, and doing request directly on postman it also works.
It was missing this line in my Application_BeginRequest
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Credentials", "true");
And in every request on AngularJS I should pass withCredentials parameter as true. To achieve that, I put this line on my config file in AngularJS:
$httpProvider.defaults.withCredentials = true;
I have implemented my own custom MembershipProvider, configured it within web.config and my initialize method is successfully being called, even though it only contains:
public override void Initialize(string name, NameValueCollection config)
{
base.Initialize(name, config);
}
I have also implemented all the other standard methods like:
public override bool ChangePassword(string username, string oldPassword, string newPassword)
and
public override bool ValidateUser(string username, string password)
My understanding of the benefits of bothering to write a custom MembershipProvider was that once you've implemented the interface and initialized it, the appropriate methods would automatically get called at the appropriate times by the standard MVC 5 web site. I.e. when a user logs in or registers etc. But none of my methods are being called, apart from Initialize()...
Am I expected to have to go through the standard AccountController.cs and change all of the methods in there to force it to call my custom MembershipProvider?
E.g. this is the standard Login() method:
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindAsync(model.Email, model.Password);
if (user != null)
{
await SignInAsync(user, model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Do I have to modify this to make sure its calling my stuff?
Or what am I missing?
Thanks.
On your web.config at the bottom of your solution, paste this under <system.web>
//This is just easy to modify
<membership defaultProvider="YourCustomMembershipProvider">
<providers>
<clear /> //this clears up all the default membership called on init
<add name="YourCustomMembershipProvider" passwordFormat="Hashed" type="ProjectName.YourFolder.YourCustomMembershipProvider" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
<roleManager enabled="true" defaultProvider="YourCustomRoleProvider">
<providers>
<clear />
<add name="YourCustomRoleProvider" type="ProjectName.YourFolder.YourCustomRoleProvider" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" writeExceptionsToEventLog="false" />
</providers>
</roleManager>
Hope this helps. :)
I find this article to be useful for non-ajax request How to handle session expiration and ViewExpiredException in JSF 2?
but I can't make use of this when I am submitting using an AJAX call.
Suppose in a primefaces dialog, I am making a post request using AJAX and session has already timed out.
I see my page getting stuck.
How to fix this kind of scenario such that when I post using AJAX, I could redirect him to my view expired page and
then forward him to the login page similar to the solution in the link above?
JSF2/Primefaces/Glassfish
Exceptions which are thrown during ajax requests have by default totally no feedback in the client side. Only when you run Mojarra with project stage set to Development and use <f:ajax>, then you will get a bare JavaScript alert with the exception type and message. But other than that, and in PrimeFaces, there's by default no feedback at all. You can however see the exception in the server log and in the ajax response (in the webbrowser's developer toolset's "Network" section).
You need to implement a custom ExceptionHandler which does basically the following job when there's a ViewExpiredException in the queue:
String errorPageLocation = "/WEB-INF/errorpages/expired.xhtml";
context.setViewRoot(context.getApplication().getViewHandler().createView(context, errorPageLocation));
context.getPartialViewContext().setRenderAll(true);
context.renderResponse();
Alternatively, you could use the JSF utility library OmniFaces. It has a FullAjaxExceptionHandler for exactly this purpose (source code here, showcase demo here).
See also:
Why use a JSF ExceptionHandlerFactory instead of <error-page> redirection?
What is the correct way to deal with JSF 2.0 exceptions for AJAXified components?
A merge between the answer of #BalusC and this post, I solved my problem!
My ExceptionHandlerWrapper:
public class CustomExceptionHandler extends ExceptionHandlerWrapper {
private ExceptionHandler wrapped;
CustomExceptionHandler(ExceptionHandler exception) {
this.wrapped = exception;
}
#Override
public ExceptionHandler getWrapped() {
return wrapped;
}
#Override
public void handle() throws FacesException {
final Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator();
while (i.hasNext()) {
ExceptionQueuedEvent event = i.next();
ExceptionQueuedEventContext context
= (ExceptionQueuedEventContext) event.getSource();
// get the exception from context
Throwable t = context.getException();
final FacesContext fc = FacesContext.getCurrentInstance();
final Map<String, Object> requestMap = fc.getExternalContext().getRequestMap();
final NavigationHandler nav = fc.getApplication().getNavigationHandler();
//here you do what ever you want with exception
try {
//log error ?
//log.log(Level.SEVERE, "Critical Exception!", t);
if (t instanceof ViewExpiredException) {
requestMap.put("javax.servlet.error.message", "Session expired, try again!");
String errorPageLocation = "/erro.xhtml";
fc.setViewRoot(fc.getApplication().getViewHandler().createView(fc, errorPageLocation));
fc.getPartialViewContext().setRenderAll(true);
fc.renderResponse();
} else {
//redirect error page
requestMap.put("javax.servlet.error.message", t.getMessage());
nav.handleNavigation(fc, null, "/erro.xhtml");
}
fc.renderResponse();
// remove the comment below if you want to report the error in a jsf error message
//JsfUtil.addErrorMessage(t.getMessage());
} finally {
//remove it from queue
i.remove();
}
}
//parent hanle
getWrapped().handle();
}
}
My ExceptionHandlerFactory:
public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory {
private ExceptionHandlerFactory parent;
// this injection handles jsf
public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) {
this.parent = parent;
}
#Override
public ExceptionHandler getExceptionHandler() {
ExceptionHandler handler = new CustomExceptionHandler(parent.getExceptionHandler());
return handler;
}
}
My faces-config.xml
<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
<factory>
<exception-handler-factory>
your.package.here.CustomExceptionHandlerFactory
</exception-handler-factory>
</factory>
</faces-config>
I am using Mojarra 2.1.7 in Production mode with JBoss 7. After the session expires, AJAX calls return an error XML document. You can easily catch this error using the usual onerror handler of f:ajax.
<script type="text/javascript">
function showError(data) {
alert("An error happened");
console.log(data);
}
</script>
<h:commandLink action="...">
<f:ajax execute="..." render="..." onerror="showError"/>
</h:commandLink>
I have included this in my ViewExpiredExceptionHandler class and it worked fine for me in WAS
public void handle() throws FacesException {
FacesContext facesContext = FacesContext.getCurrentInstance();
for (Iterator<ExceptionQueuedEvent> iter = getUnhandledExceptionQueuedEvents()
.iterator(); iter.hasNext();) {
Throwable exception = iter.next().getContext().getException();
if (exception instanceof ViewExpiredException) {
final ExternalContext externalContext = facesContext
.getExternalContext();
try {
facesContext.setViewRoot(facesContext.getApplication()
.getViewHandler()
.createView(facesContext, "/Login.xhtml")); //Login.xhtml is the page to to be viewed. Better not to give /WEB-INF/Login.xhtml
externalContext.redirect("ibm_security_logout?logoutExitPage=/Login.xhtml"); // when browser back button is pressed after session timeout, I used this.
facesContext.getPartialViewContext().setRenderAll(true);
facesContext.renderResponse();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
iter.remove();
}
}
}
getWrapped().handle();
}
Hope this helps
I faced this problem, Requirement need to display a confirmation popup when user do any action after session gets timed out, my proposed solution was:
<security:http use-expressions="true" auto-config="true" entry-point-ref="authenticationEntryPoint">
<security:intercept-url pattern="/common/auth/**" access="permitAll" />
<security:intercept-url pattern="/javax.faces.resource/**" access="permitAll" />
<security:intercept-url pattern="/**/ *.*" access="hasRole('ROLE_ADMIN')" />
<security:form-login login-page="/common/auth/login.jsf" />
<!-- <security:remember-me key="secret" services-ref="rememberMeServices" /> -->
<security:logout invalidate-session="true" logout-success-url="/common/auth/login.jsf" />
</security:http>
<bean id="authenticationEntryPoint" class="com.x.y.MyRedirectEntryPoint" >
<property name="loginFormUrl" value="/common/auth/login.jsf"/>
</bean>
The MyRedirectEntryPoint should extends AuthenticationProcessingFilterEntryPoint and override commence method
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
boolean ajaxRedirect = request.getHeader("faces-request") != null
&& request.getHeader("faces-request").toLowerCase().indexOf("ajax") > -1;
if (ajaxRedirect) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
response.sendError(403);
}
} else {
super.commence(request, response, authException);
}
}
Now you can simply bind a callback javascript function to catch the thrown 403 error and do what ever you want:
$(document).bind('ajaxError',
function(event, request, settings, exception){
if (request.status==403){
//do whatever you wanted may be show a popup or just redirect
window.location = '#{request.contextPath}/';
}
});
For me a simple client side javascript handler worked:
function handleAjaxExpired(xhr,status,args) {
// handler for "oncomplete" ajax callback
if ( xhr.responseXML.getElementsByTagName('error-name').length ) {
// "<error-name>" tag is present -> check for "view expired" exception
html = xhr.responseXML.getElementsByTagName('error-name')[0].innerHTML;
if ( html.indexOf('ViewExpiredException') > -1 ) {
// view expired exception thrown
// do something / reload page
if ( confirm('session expired -> reload page?') ) {
document.location.href=document.location.href;
}
}
}
}
This handler is called from "oncomplete" attribute in triggering UI element, e.g. here from a rowSelect event in a Primefaces datatable:
<p:ajax event="rowSelect" oncomplete="handleAjaxExpired(xhr,status,args)" />
Update: To avoid adding "oncomplete" attributes to every ajax-enabled element, this javascript code searches globally in all ajax responses for errors:
(function() {
// intercept all ajax requests
var origXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
this.addEventListener('load', function() {
handleAjaxExpired(this);
});
origXHROpen.apply(this, arguments);
};
})();
This code makes "oncomplete" attributes in PrimeFaces UI-elements obsolete.
I am trying the MVC3 exception handling, and came with following test code:
public class HomeController : Controller {
public ActionResult Index() {
throw new ArgumentException();
return View();
}
}
My controller forces to throw exception, and in my web.config
<customErrors mode="On" defaultRedirect="~/ErrorHandler/Index">
<error statusCode="404" redirect="~/ErrorHandler/NotFound"></error>
</customErrors>
I have created another controller to server ErrorHandler/Index and ErrorHandler/NotFound request already.
With my testing, I can see the 404 code could be captured but 500 code has been ignore totally.
Anything wrong with my code?
Thanks
Hardy
Remove the following line:
filters.Add(new HandleErrorAttribute());
from the RegisterGlobalFilters method in your Global.asax.