I have a error page with layout that works fine in most cases but when there is an error in a controller that returns a partial view the error page and its layout is placed in the partial view. I guess thats logical but I want the error page to be loaded as full page. How do I accomplish that without changing all error handling.
web.config:
<customErrors mode="On" defaultRedirect="~/Error">
<error statusCode="500" redirect="~/SystemPages/ErrorPage" />
<error statusCode="403" redirect="~/SystemPages/FileNotFound" />
<error statusCode="404" redirect="~/SystemPages/FileNotFound" />
</customErrors>
Global.asax:
Shared Sub RegisterGlobalFilters(ByVal filters As GlobalFilterCollection)
filters.Add(New HandleErrorAttribute())
End Sub
BaseController:
Protected Overrides Sub OnException(ByVal filterContext As ExceptionContext)
If filterContext Is Nothing Then Return
If TypeOf (filterContext.Exception) Is FaultException Then
Dim CodeName As String =
CType(filterContext.Exception, FaultException).Code.Name
Dim Message As String = CType(filterContext.Exception, FaultException).Message
TempData("ErrorMessage") = Message
Else
Logging.LogDebugData(HamtaDebugInformation(filterContext.RouteData))
Logging.WriteExceptionLog(filterContext.Exception)
TempData("ErrorMessage") = filterContext.Exception.Message
End If
Response.Redirect("/SystemPages/ErrorPage")
End Sub
SearchController:
Function GetData() As ActionResult
...
Return PartialView("_Tab", vmData)
ErrorPage:
#Code
ViewData("Title") = "ErrorPage"
Layout = "~/Views/Shared/_Layout.vbhtml"
End Code
<div id="mainContent" class="oneColumn">
<div class="panel">
<span class="panelTLC"></span>
<span class="panelTRC"></span>
<div id="inputPanel" class="panelContent">
<div class="modul">
<div class="modulHead">
<span class="TLC"></span>
<span class="TRC"></span>
</div>
<div class="modulContent">
<span class="TLC"></span><span class="TRC"></span>
<p>#ViewBag.ErrorMessage</p>
<p>#TempData("ErrorMessage")</p>
<span class="BLC"></span>
<span class="BRC"></span>
</div>
</div>
</div>
<span class="panelBLC"></span><span class="panelBRC"></span>
</div>
</div>
You could just use a try catch block and in the catch return a View() instead of PartialView().
Function GetData() As ActionResult
Try
...
Return PartialView("_Tab", vmData)
Catch ex as Exception
//Handle exception here ( send to error log, etc)
Return View("~/SystemPages/ErrorPage")
End Try
OR
web.config:
<customErrors mode="On"/>
BaseController:
Protected Overrides Sub OnException(ByVal filterContext As ExceptionContext)
If filterContext Is Nothing Then Return
Dim Message As String
If TypeOf (filterContext.Exception) Is FaultException Then
Dim CodeName As String =
CType(filterContext.Exception, FaultException).Code.Name
Message = CType(filterContext.Exception, FaultException).Message
Else
Logging.LogDebugData(HamtaDebugInformation(filterContext.RouteData))
Logging.WriteExceptionLog(filterContext.Exception)
Message = filterContext.Exception.Message
End If
Response.Redirect(String.Format("~/Error/HttpError/?message={1}", "HttpError", Message))
End Sub
ErrorController:
public class ErrorController : Controller
{
// GET: /Error/HttpError
public ActionResult HttpError(string message) {
return View("ErrorTest", message);
}
This post: ASP.NET MVC Custom Error Handling Application_Error Global.asax?
goes into how to handle each type of error separately. Keep in mind you are handling your exceptions in basecontroller instead of the global.asax file. Which if you were able to change your exception handling, that would be the better way to do it.
Related
I'm using this construct to host multiple forms in the same view:
When I submit the first form without entering credentials, validation fails. The form reloads as a GET, with a URL of /home/signin, and the expected error messages don't display.
The controller's ModelState is invalid as expected, but I can't figure out why this state isn't passed back to the view.
What am I doing wrong? How can I get validation to work with multiple forms?
Here's my code:
Controller
Imports System.Threading.Tasks
Imports System.Web.Mvc
Imports Website.ViewModels.Home
Imports Microsoft.AspNet.Identity.Owin
Namespace Controllers.Home
Partial Public Class HomeController
<AllowAnonymous>
Public Function Index() As ActionResult
Dim oViewModel As SignInOrSignUp
oViewModel = New SignInOrSignUp
Return Me.View("Index", oViewModel)
End Function
<HttpPost>
<ValidateAntiForgeryToken>
Public Async Function SignIn(Model As SignIn) As Task(Of ActionResult)
Dim oViewModel As SignInOrSignUp
Dim eStatus As SignInStatus
Dim oAction As ActionResult
oViewModel = New SignInOrSignUp With {.SignIn = Model}
If Me.ModelState.IsValid Then
eStatus = Await Me.SignInManager.PasswordSignInAsync(Model.Username, Model.Password, isPersistent:=False, shouldLockout:=False)
If eStatus = SignInStatus.Success Then
oAction = Me.Redirect("www.domain.com")
Else
Me.ModelState.AddModelError("", "Invalid signin attempt.")
oAction = Me.View("Index", oViewModel)
End If
Else
oAction = Me.View("Index", oViewModel)
End If
Return oAction
End Function
End Class
End Namespace
ViewModel
Imports System.ComponentModel.DataAnnotations
Namespace ViewModels.Home
Public Class SignIn
<Required(ErrorMessage:="User Name is required.")>
<Display(Name:="User Name")>
Public Property Username As String
<Required>
Public Property Password As String
<Display(Name:="Remember Me")>
Public Property RememberMe As Boolean
End Class
End Namespace
View
#ModelType ViewModels.Home.SignInOrSignUp
#Code
Layout = Nothing
End Code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Website</title>
#Scripts.Render("~/scripts/bootstrap")
#Scripts.Render("~/scripts/jquery")
#Styles.Render("~/styles/bootstrap")
#Styles.Render("~/styles/home")
</head>
<body>
<div class="row">
<div class="wrapper">
<div class="section">
<h5>Sign in here</h5>
<div class="box">
<div class="titlebar">Sign In</div>
#Using (Html.BeginForm("signin", "home", FormMethod.Post, New With {.id = "SignInForm"}))
#Html.AntiForgeryToken
#Html.EditorFor(Function(M) Model.SignIn)
#<div class="button">
<input type="submit" value="Sign In" Class="button" />
</div>
#<div class="anchor">
Forgot your password?
</div>
End Using
</div>
</div>
<div class="section">
<h5>Or enter your City Code here</h5>
<div class="box">
<div class="titlebar">Sign Up</div>
#Using (Html.BeginForm("signup", "home", FormMethod.Post, New With {.id = "SignUpForm"}))
#Html.AntiForgeryToken
#Html.EditorFor(Function(M) Model.SignUp)
#<div class="button">
<input type="submit" value="Continue" Class="button" />
</div>
End Using
</div>
</div>
</div>
</div>
</body>
</html>
--EDIT--
I've been able to get unblocked in the meantime with a clunky workaround in my view:
#Code
If IsPost AndAlso Request.Path = "/home/signin" AndAlso Model.SignIn.Username.IsNothing Then
#Html.ValidationMessageFor(Function(Model) Model.SignIn.Username, "Username is required", New With {.class = "text-danger"})
End If
End Code
But I'd still like to use the in-built validation mechanism if it's possible to do so with multiple forms in the same view.
There are 2 things you can try:
1. Put validation summary on the page:
With this option you are not changing anything in your controller but you are putting error summary on your page. You will not get highlighted field but you will still be able to display all errors, like:
<body>
<div class="row">
<div class="wrapper">
#Html.ValidationSummary(false)
...
</div>
</div>
</body>
Not an elegant solution!
2. In controller use SignInOrSignUp as an input but from UI provide either SignIn or SignUp model:
With this approach you are not changing your vbhtml/cshtml file it will remain as it is only thing you are changing is type of argument on controller methods:
Namespace Controllers.Home
Partial Public Class HomeController
...
<HttpPost>
<ValidateAntiForgeryToken>
Public Async Function SignIn(Model As SignInOrSignUp) As Task(Of ActionResult)
...
End Function
<HttpPost>
Public Async Function SignUp(Model As SignInOrSignUp) As Task(Of ActionResult)
...
End Function
End Class
End Namespace
unless there is some complex validation involved, this approach will work like a charm!
I've an MVC app that I'm trying to get it to use salesforce as the sso idp. I'm able to get it to redirect to salesforce for login. But after I've logging in, the following do not work as I expected:
1) The User.Identity.IsAuthenticated remains false. So, the index page is displaying "You are not login yet." even if I've login.
2) If I click on About link, it redirect me to the index page if I've already signin. If not, it takes me to salesforce to login and then takes me to the index page, which will display "You are not login yet".
Below are some info about my MVC app, would appreciate any help.
Web.config:
<sustainsys.saml2 entityId="http://xxxx.xxxx.com/SAML2" returnUrl="http://localhost:51048/Home/Index" authenticateRequestSigningBehavior="Never">
<nameIdPolicy allowCreate="true" format="Persistent" />
<metadata cacheDuration="PT1H" validDuration="7.12:00:00" wantAssertionsSigned="true">
<organization name="xxxx Inc" displayName="xxxx" url="https://www.xxxx.com" language="en" />
<contactPerson type="Support" email="lim.hock-chai#xxxx.com" />
<requestedAttributes>
<add friendlyName="User Name" name="urn:Username" nameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="true" />
<add name="Minimal" />
</requestedAttributes>
</metadata>
<identityProviders>
<add entityId="https://xxxx.my.salesforce.com" signOnUrl="https://xxxx.xxx.my.salesforce.com/idp/login?app=xxxxxxxxxxx" allowUnsolicitedAuthnResponse="true" binding="HttpRedirect">
<signingCertificate fileName="~/App_Data/SelfSignedCert_10Oct2017.crt" />
</add>
</identityProviders>
</sustainsys.saml2>
Home Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[Authorize]
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
Index page:
#{
ViewBag.Title = "Home Page";
}
<div class="row">
#if (User.Identity.IsAuthenticated)
{
<div class="col-md-4">
<h2>Hello #User.Identity.Name</h2>
<p>
Welcome.
</p>
</div>
}
else
{
<div class="col-md-4">
<h2>Hello</h2>
<p>
You are not login yet. Click here to login
</p>
</div>
}
</div>
About Page:
#{
ViewBag.Title = "About";
}
<h2>#ViewBag.Title.</h2>
<h3>#ViewBag.Message</h3>
<p>Use this area to provide additional information.</p>
I found the cause of the issue. It was caused by me putting the wrong acs link when setting up connected app in salesforce. The acs link has to be /Saml2/Acs for it to work (for example:http://mysite/Saml2/Acs). This fixed my "User.Identity.IsAuthenticated remains false" issue. But I encountered two additional issues after that was fixed.
One is related to salesforce using SHA1 encryption that is unacceptable for saml2. For that I added the minIncomingSigningAlgorithm="SHA1" attribute to the sustainsys.saml2 element in web.config to by pass the issue:
<sustainsys.saml2 .... minIncomingSigningAlgorithm="SHA1">
The other issue is related to salesforce not including InResponseTo element in the response. For this, I added the ignoreMissingInResponseTo attribute to by pass the error:
<sustainsys.saml2 ....>
....
<compatibility ignoreMissingInResponseTo="true">
</compatibility>
....
</sustainsys.saml2>
I am working on MVC Application.
I have FilterConfig class :
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
}
I am using it in Global.asax.cs
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
If i use this approach my Application_Error does not get fired when any exception occurs in controller.
protected void Application_Error()
{
var exception = Server.GetLastError();
AppLogging.WriteError(exception);
var httpException = exception as HttpException;
Response.Clear();
Server.ClearError();
var routeData = new RouteData();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "Error";
routeData.Values["exception"] = exception;
Response.StatusCode = 500;
if (httpException != null)
{
Response.StatusCode = httpException.GetHttpCode();
switch (Response.StatusCode)
{
case 403:
routeData.Values["action"] = "UnauthorizedAccess";
break;
case 503:
routeData.Values["action"] = "SiteUnderMaintenance";
break;
case 404:
routeData.Values["action"] = "PageNotFound";
break;
}
}
// Avoid IIS7 getting in the middle
Response.TrySkipIisCustomErrors = true;
IController errorsController = new ErrorController();
HttpContextWrapper wrapper = new HttpContextWrapper(Context);
var rc = new RequestContext(wrapper, routeData);
errorsController.Execute(rc);
}
public ActionResult Error()
{
return View("Error");
}
Now when i do customErrors mode="Off" it goes to Application_Error Event but HandleErrorInfo comes as null.
Error.cshtml
#model HandleErrorInfo
#{
Layout = "~/Views/Shared/_LayoutAnonymous.cshtml";
ViewBag.Title = "Error";
}
<div class="error_wrapper">
<div class="col-sm-12 col-xs-12">
<div class=" error_col">
<div style="display: none;">
#if (Model != null)
{
<h3>#Model.Exception.GetType().Name</h3>
<pre>
#Model.Exception.ToString()
</pre>
<p>
thrown in #Model.ControllerName #Model.ActionName
</p>
}
</div>
<h1 class="error_h1">503 </h1>
<h2 class="error_h2">Looks like we're having some server issues. </h2>
<h3 class="error_h3">
Go back to the previous page and try again.<br>
If you think something is broken, report a problem.
</h3>
<div class="col-sm-12 col-xs-12 padding_none">
<button class="btn btn-primary btn_box error_btn" id="btnReport">Report A Problem</button>
<button class="btn btn-primary btn_box error_btn pull-left" onclick="location.href='#Url.Action("Requests", "Pricing", new RouteValueDictionary { { "area", "" } })'"> Go To Homepage</button>
</div>
</div>
</div>
</div>
#Scripts.Render("~/bundles/Error")
This is because your web config settings <customErrors mode="On" /> are overriding the default behavior. You will need to disable this setting. if you disable this, you can handle the error in the Application_Error event then redirect the user or display a message from this event. This web config setting is handling the errors and only unhandled errors will bubble up to the Application_Error event.
I'm trying to handle eventual errors in my view, by using the HandleError attribute on my view:
The reason why the Action is called 'Error' is because it gets a list of logged errors from a database.
[HandleError]
public ActionResult Error(int? page)
{
var errors = errorRepository.GetErrors();
// stuff for paging
var pageSize = 10;
var pageNumber = (page ?? 1); // if there is no page, return page 1
return View("Error", errors.ToPagedList(pageNumber, pageSize));
}
This is the error page in the /Shared/ folder:
#model System.Web.Mvc.HandleErrorInfo
#{
ViewBag.Title = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
But for some reason, the error page is never being shown, even though I've forced an exception in the action method. It just goes to the default url in my RouteConfig file.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Any hint as to why it doesn't show my error page is greatly appreciated!
I am sorry I have to add this as answer, but I don't have enough points to comment.
To be able to help you I need to see the code within the HandleErrorAttribute. However what you normally want to do in these cases is:
1) Add a config setting in the web.config to say that you will handle the exceptions on your own. Something like:
<system.web>
<customErrors mode="On" defaultRedirect="~/Error">
<error statusCode="500" redirect="~/Error/InternalServer" />
<error statusCode="404" redirect="~/Error/NotFound" />
</customErrors>
</system.web>
2) Add the methods to accept those incoming calls in the ErrorController (In this case Index(), InternalServer(), NotFound())
3) Get the logs from your database and display them to the user than
I have a custom Errors controller that looks like this:
public class ErrorsController : BaseController
{
public ActionResult RaiseError(string error = null)
{
string msg = error ?? "An error has been thrown (intentionally).";
throw new Exception(msg);
}
public ActionResult Error404()
{
Response.TrySkipIisCustomErrors = true;
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View();
}
public ActionResult Error500()
{
Response.TrySkipIisCustomErrors = true;
var model = new Models.Errors.Error500()
{
ServerException = Server.GetLastError(),
HTTPStatusCode = Response.StatusCode
};
return View(model);
}
}
My Errors500.cshtml looks like this:
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Error500</title>
</head>
<body>
<div>
An internal error has occurred.
#if (Model != null && Model.ServerException != null && HttpContext.Current.IsDebuggingEnabled)
{
<div>
<p>
<b>Exception:</b> #Model.ServerException.Message<br />
</p>
<div style="overflow:scroll">
<pre>
#Model.ServerException.StackTrace
</pre>
</div>
</div>
}
</div>
</body>
</html>
and my web.config has my error handlers specified as such:
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace" >
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" subStatusCode="-1" responseMode="ExecuteURL" path="/Errors/Error404" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="500" subStatusCode="-1" responseMode="ExecuteURL" path="/Errors/Error500" />
</httpErrors>
The problem is: everytime I call /errors/raiseerror to test my 500 handling; I'm redirected to errors/error500 (fine). However, the exception data isn't rendered on the page because the Server.GetLastError() call returns null instead of the exception thrown by RaiseError().
What's the best way to handle a custom 500 error page where that custom page can render out the exception details as well?
The easiest way to go about this is:
Use MVC's built-in support to handle Exceptions. By default MVC uses HandleErrorAttribute that is registered in App_Start\FilterConfig.cs:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
Now make sure you have a view called Error in Views\Shared folder. The view by default has model of type HandleErrorInfo with a property named Exception. You can show the Exception message and other details if you want like this:
Error.cshtml
#model HandleErrorInfo
#if(Model != null)
{
#Model.Exception.Message
}
You can customize the Error.cshtml page the way you want...