Return shared view from ViewComponent - asp.net-mvc

I have an Error-view which should be loaded if an Exception occurs. The Error view is located in:
Views/Shared/Error.cshtml (See attached picture).
In my Controller, my try-and catch looks like this:
public IActionResult Device(string id, bool like, int type)
{
try
{
//code
return View(viewModel);
}
catch (Exception exe)
{
return View("Error", exe);
}
}
This works and the correct Error View is displayed. However, I have a ViewComponent which should display the same Error-view.
I have tried the following:
1) Copy the Error-file and pasted it in the same folder as my ViewComponent (Right beneath the Default view). This does not give me an error, but the Default-view is the one being loaded.
2) I have returned the Error view from the shared-folder in the following way:
return View("../../../Shared/Error");
This as well does not give errors, but the Default view is the one being loaded.
Any ideas on how to solve this?
EDIT
So far I have created a new class
public class HandleExceptionAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var result = new ViewResult { ViewName = "Error" };
var modelMetadata = new EmptyModelMetadataProvider();
result.ViewData = new ViewDataDictionary(
modelMetadata, context.ModelState);
result.ViewData.Add("HandleException",
context.Exception);
context.Result = result;
context.ExceptionHandled = true;
}
}
And in my Error View I added this:
#{
ViewData["Title"] = "Error";
Layout = "_LayoutCustomer";
Exception ex = ViewData["HandleException"] as Exception;
}
And lastly, I added [HandleException] on top of my Controller:
[HandleException]
public class CustomerController : Controller
{
//All the actions...
}
To simulate a new Exception, I use:
public IActionResult Device(string id, bool like, int type)
{
try
{
throw new Exception();
//code
return View(viewModel);
}
catch (Exception exe)
{
throw;
}
}
This seems to work in the Controller-actions. How can I simulate if it works within the ViewComponent? It gives me an error when I do the same try-catch method.

Step 1 : - Create a Custom ExpectionFilter Attribute
public class CustomExpectionFilter : IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
filterContext.Result = new ViewResult
{
ViewName = "~/Views/Shared/Error.cshtml"
};
}
}
Step 2 :- Register CustomExpectionFilter in FilterConfig
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomExpectionFilter());
}
}
Step 3 :- Change on
public IActionResult Device(string id, bool like, int type)
{
try
{
//code
return View(viewModel);
}
catch (Exception)
{
throw;
}
}
Now when ever error occur in application it will be calling CustomExpection Filter and this filter will handle error and display Error page.

Related

Get action local variables name and value OnException customized filter

Want to retrieve the variable value of action into customized filter
public class TrackError : IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
// How to get the value of X ?????????????????
}
}
Controller :
[TrackError]
public class HomeController : Controller
{
public ActionResult Index()
{
int x = 0;
throw new Exception("XYZ");
return View();
}
}
Have you tried this way.
int x = 0;
try
{
DoSomethingThatMightFail(s);
}
catch (Exception ex) when (Log(ex, "An error occurred", new[]{x,s}))
{
// this catch block will never be reached
}
...
static bool Log(Exception ex, string message, params object[] args)
{
Debug.Print(message, args);
return false;
}
Create custom exception and put whatever additional data you need into its properties. Then you catch that exception type in one of your catch blocks within your exception filter.
public class ServiceException : Exception, ISerializable
{
public WhateverType X {get;set;}
public string Message{get;set;}
public ServiceException()
{
// Add implementation.
}
public ServiceException(WhateverType x, string message)
{
this.X = x;
this.Message = message;
}
public ServiceException(string message):base(message)
{
}
public ServiceException(string message, Exception inner)
{
// Add implementation.
}
// This constructor is needed for serialization.
protected ServiceException(SerializationInfo info, StreamingContext context)
{
// Add implementation.
}
}
and then in filter:
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is ServiceException)
{
//Here you can access (context.Exception as ServiceException).X
}
}
throw your exception like:
throw new ServiceException(X, "Your custom message gore here");

Error handling last chance to catch View Rendering exception

I've got ErrorController which customly handles my website errors.
It's pretty standard:
public class ErrorController : BaseController
{
public ActionResult Error404(Exception ex)
{
return View();
}
public ActionResult Error500(Exception ex)
{
return View();
}
}
However, in case if some rendering exception occurs inside of the View code (and this might occur, as the page has Master page (master layout) and different might happen), then I am not able to catch that rendering exception.
I can really see that exception with implementing ActionFilterAttribute.OnResultExecuted:
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Exception != null)
{
// not sure what to do here
} else base.OnResultExecuted(filterContext);
}
but in that case MVC looks for ~/Shared/Error.cshtml (incl. this path) after that exception occurs, and I can't provide the Errors view rendering exception to the user -- the "Last chance exception".
Is there any way to handle that?
Here is a nice article on Exception handling in ASP.Net MVC that should help
Method 4:- Inheriting from “HandleErrorAttribute”
public class CustomHandleErrorAttribute: HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
Exception ex = filterContext.Exception;
filterContext.ExceptionHandled = true;
var model = new HandleErrorInfo(filterContext.Exception, "Controller", "Action");
filterContext.Result = new ViewResult()
{
ViewName = "Error",
ViewData = new ViewDataDictionary(model)
};
}
}
And you attach that to your base controller.

Execute action in other controller on 404

I'm trying to return a action "PageNotFound" that resides in my "Error"-controller.
public class BaseController : Controller
{
public BaseController()
{
}
public BaseController(IContentRepository contentRep, ILocalizedRepository localRep)
{
this._localRep = localRep;
this._contentRep = contentRep;
}
protected new HttpNotFoundResult HttpNotFound(string statusDescription = null)
{
return new HttpNotFoundResult(statusDescription);
}
protected HttpUnauthorizedResult HttpUnauthorized(string statusDescription = null)
{
return new HttpUnauthorizedResult(statusDescription);
}
protected class HttpNotFoundResult : HttpStatusCodeResult
{
public HttpNotFoundResult() : this(null) { }
public HttpNotFoundResult(string statusDescription) : base(404, statusDescription) { }
}
protected class HttpUnauthorizedResult : HttpStatusCodeResult
{
public HttpUnauthorizedResult(string statusDescription) : base(401, statusDescription) { }
}
protected class HttpStatusCodeResult : ViewResult
{
public int StatusCode { get; private set; }
public string StatusDescription { get; private set; }
public HttpStatusCodeResult(int statusCode) : this(statusCode, null) { }
public HttpStatusCodeResult(int statusCode, string statusDescription)
{
this.StatusCode = statusCode;
this.StatusDescription = statusDescription;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
context.HttpContext.Response.StatusCode = this.StatusCode;
if (this.StatusDescription != null)
{
context.HttpContext.Response.StatusDescription = this.StatusDescription;
}
this.ViewName = "PageNotFound"; // CONTROLLER MISSING
this.ViewBag.Message = context.HttpContext.Response.StatusDescription;
base.ExecuteResult(context);
}
}
How can I modify it so it returns the "PageNotFound" action in the "Error"- controller?
A ViewResult is supposed to directly render a view (optionally passing a model and a layout). There's no controller involved in this process.
If you want to go through a controller you need to perform redirect, i.e. use RedirectToRouteResult instead of ViewResult.
In your example you are using this custom ViewResult directly inside some other controller. So that will be the controller that will render the error view.
I dont understand why you want to make a redirect. I would return 404
return HttpStatusCode(404);
And then use the approach described here: ASP.NET MVC 404 Error Handling to render the correct view. Benefit: your url is still the same, much easier for error handling and for the browser history.
Have you tried
return RedirectToAction("PageNotFound", "ControllerName");

How do I Redirect to another Action/Controller in MVC3 from a VOID method?

I have a controller method that returns a void because it is building an Excel report for the user to download. The Excel 3rd party library we're using is writing to the response itself. The method looks something like this:
[HttpGet]
public void GetExcel(int id)
{
try
{
var report = _reportService.GetReport(id);
var table = _reportService.GetReportTable(id);
var excelReport = new ExcelReport(table, report.Name);
excelReport.DownloadReport(System.Web.HttpContext.Current.Response);
}
catch (Exception ex)
{
// This is wrong, of course, because I'm not returning an ActionResult
Response.RedirectToRoute("/Report/Error/", new { exceptionType = ex.GetType().Name });
}
}
There are several security checks in place that throw exceptions if the user doesn't meet certain credentials for fetching the report. I want to redirect to a different page and pass along some information about the exception, but I can't figure out how to do this in MVC3....
Any ideas?
You could use the following code
Response.Redirect(Url.Action("Error", "Report", new { exceptionType = ex.GetType().Name }));
But have you taken a look at the FilePathResult or FileStreamResult ?
Instead of letting the 3rd part library write to the response directly get the content use regular ActionResult and return File(...) for the actual file or RedirectToAction(...) (or RedirectToRoute(...)) on error. If your 3rd party library can only write to Response you may need to use some tricks to capture it's output.
[HttpGet]
public ActionResult GetExcel(int id)
{
try
{
var report = _reportService.GetReport(id);
var table = _reportService.GetReportTable(id);
var excelReport = new ExcelReport(table, report.Name);
var content = excelReport.MakeReport(System.Web.HttpContext.Current.Response);
return File(content, "application/xls", "something.xls");
}
catch (Exception ex)
{
RedirectToRoute("/Report/Error/", new { exceptionType = ex.GetType().Name });
}
}
You can return an EmptyActionResult:
[HttpGet]
public ActionResult GetExcel(int id)
{
try
{
var report = _reportService.GetReport(id);
var table = _reportService.GetReportTable(id);
var excelReport = new ExcelReport(table, report.Name);
excelReport.DownloadReport(System.Web.HttpContext.Current.Response);
return new EmptyResult();
}
catch (Exception ex)
{
return RedirectToAction("Error", "Report", rnew { exceptionType = ex.GetType().Name });
}
}
Not sure if it works, haven't tested it.
Another approach would be using an exception filter:
public class MyExceptionFilter : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
var routeValues = new RouteValueDictionary()
{
{ "controller", "Error" },
{ "action", "Report" }
};
filterContext.Result = new RedirectToRouteResult(routeValues);
filterContext.ExceptionHandled = true;
// Or I can skip the redirection and render a whole new view
//filterContext.Result = new ViewResult()
//{
// ViewName = "Error"
// //..
//};
}
}

ASP.NET MVC unit test controller with HttpContext

I am trying to write a unit test for my one controller to verify if a view was returned properly, but this controller has a basecontroller that accesses the HttpContext.Current.Session. Everytime I create a new instance of my controller is calls the basecontroller constructor and the test fails with a null pointer exception on the HttpContext.Current.Session. Here is the code:
public class BaseController : Controller
{
protected BaseController()
{
ViewData["UserID"] = HttpContext.Current.Session["UserID"];
}
}
public class IndexController : BaseController
{
public ActionResult Index()
{
return View("Index.aspx");
}
}
[TestMethod]
public void Retrieve_IndexTest()
{
// Arrange
const string expectedViewName = "Index";
IndexController controller = new IndexController();
// Act
var result = controller.Index() as ViewResult;
// Assert
Assert.IsNotNull(result, "Should have returned a ViewResult");
Assert.AreEqual(expectedViewName, result.ViewName, "View name should have been {0}", expectedViewName);
}
Any ideas on how to mock (using Moq) the Session that is accessed in the base controller so the test in the descendant controller will run?
Unless you use Typemock or Moles, you can't.
In ASP.NET MVC you are not supposed to be using HttpContext.Current. Change your base class to use ControllerBase.ControllerContext - it has a HttpContext property that exposes the testable HttpContextBase class.
Here's an example of how you can use Moq to set up a Mock HttpContextBase:
var httpCtxStub = new Mock<HttpContextBase>();
var controllerCtx = new ControllerContext();
controllerCtx.HttpContext = httpCtxStub.Object;
sut.ControllerContext = controllerCtx;
// Exercise and verify the sut
where sut represents the System Under Test (SUT), i.e. the Controller you wish to test.
If you are using Typemock, you can do this:
Isolate.WhenCalled(()=>controller.HttpContext.Current.Session["UserID"])
.WillReturn("your id");
The test code will look like:
[TestMethod]
public void Retrieve_IndexTest()
{
// Arrange
const string expectedViewName = "Index";
IndexController controller = new IndexController();
Isolate.WhenCalled(()=>controller.HttpContext.Current.Session["UserID"])
.WillReturn("your id");
// Act
var result = controller.Index() as ViewResult;
// Assert
Assert.IsNotNull(result, "Should have returned a ViewResult");
Assert.AreEqual(expectedViewName, result.ViewName, "View name should have been {0}", expectedViewName);
}
Snippet:
var request = new SimpleWorkerRequest("/dummy", #"c:\inetpub\wwwroot\dummy", "dummy.html", null, new StringWriter());
var context = new HttpContext(request);
SessionStateUtility.AddHttpSessionStateToContext(context, new TestSession());
HttpContext.Current = context;
Implementation of TestSession():
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.SessionState;
namespace m1k4.Framework.Test
{
public class TestSession : IHttpSessionState
{
private Dictionary<string, object> state = new Dictionary<string, object>();
#region IHttpSessionState Members
public void Abandon()
{
throw new NotImplementedException();
}
public void Add(string name, object value)
{
this.state.Add(name, value);
}
public void Clear()
{
throw new NotImplementedException();
}
public int CodePage
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public System.Web.HttpCookieMode CookieMode
{
get
{
throw new NotImplementedException();
}
}
public void CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
public int Count
{
get
{
throw new NotImplementedException();
}
}
public System.Collections.IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
public bool IsCookieless
{
get
{
throw new NotImplementedException();
}
}
public bool IsNewSession
{
get
{
throw new NotImplementedException();
}
}
public bool IsReadOnly
{
get
{
throw new NotImplementedException();
}
}
public bool IsSynchronized
{
get
{
throw new NotImplementedException();
}
}
public System.Collections.Specialized.NameObjectCollectionBase.KeysCollection Keys
{
get
{
throw new NotImplementedException();
}
}
public int LCID
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public SessionStateMode Mode
{
get
{
throw new NotImplementedException();
}
}
public void Remove(string name)
{
this.state.Remove(name);
}
public void RemoveAll()
{
this.state = new Dictionary<string, object>();
}
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
public string SessionID
{
get
{
return "Test Session";
}
}
public System.Web.HttpStaticObjectsCollection StaticObjects
{
get
{
throw new NotImplementedException();
}
}
public object SyncRoot
{
get
{
throw new NotImplementedException();
}
}
public int Timeout
{
get
{
return 10;
}
set
{
throw new NotImplementedException();
}
}
public object this[int index]
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public object this[string name]
{
get
{
return this.state[name];
}
set
{
this.state[name] = value;
}
}
#endregion
}
}
You should probably use an ActionFilter instead of a base class for this sort of thing
[UserIdBind]
public class IndexController : Controller
{
public ActionResult Index()
{
return View("Index.aspx");
}
}
I'd checkout the ASP.NET-MVC book listed here -- toward the end, there is a good section on Mocking framewors -- http://www.hanselman.com/blog/FreeASPNETMVCEBookNerdDinnercomWalkthrough.aspx

Resources