MVC3 Custom error pages gives blank result - asp.net-mvc-3

Using the blog posted here and a topic here on SO i've created a controller which should handle all my error pages.
In my Global.asax.cs I've got the following piece of code:
protected void Application_Error()
{
var exception = Server.GetLastError();
var httpException = exception as HttpException;
var routeData = new RouteData();
Response.Clear();
Server.ClearError();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "General";
routeData.Values["exception"] = exception;
Response.StatusCode = 500;
if (httpException != null)
{
Response.StatusCode = httpException.GetHttpCode();
switch (Response.StatusCode)
{
case 403:
routeData.Values["action"] = "Http403";
break;
case 404:
routeData.Values["action"] = "Http404";
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);
}
My ErrorController looks like this:
public class ErrorController : BaseController
{
/// <summary>
/// Returns a default view for not having access.
/// </summary>
public ActionResult Unauthorized()
{
BaseModel viewModel = new BaseModel
{
LoginModel = new LogonModel(),
ProfessionsTopX = GetTopXProfessions()
};
return View(viewModel);
}
public ActionResult General(Exception exception)
{
return View("Exception", exception);
}
public ActionResult Http404()
{
//This line works
//return Content("Not found", "text/plain");
//This line presents a blank page
return View("404","_Layout");
}
public ActionResult Http403()
{
return View("403", "_Layout");
}
}
And my Razor View only contains the piece of html below;
#{
ViewBag.Title = "404";
}
<h2>404</h2>
This is a 404 page!
When I use the Return Content i'm getting a plain textoutput telling me i'm looking at a 404-page. However, I want the 404 page to fit the rest of my design, so I want to use my own Views. However as soon as I use Return View I'm getting a blank page. I expect to be missing something very obvious, but I don't see it.

I was having the same problem, and finally found the solution that worked for me. The breakthrough came when I placed a breakpoint on the errorsController.Execute(rc); line, and used 'step into' until I came across this exception:
The view 'Detail' or its master was not found or no view engine supports the
searched locations. The following locations were searched:
~/Views/Errors/Detail.aspx
~/Views/Errors/Detail.ascx
~/Views/Shared/Detail.aspx
~/Views/Shared/Detail.ascx
~/Views/Errors/Detail.cshtml
~/Views/Errors/Detail.vbhtml
~/Views/Shared/Detail.cshtml
~/Views/Shared/Detail.vbhtml
The exception was being swallowed, I assume because it was happening inside the Application_Error method and because I had set Response.TrySkipIisCustomErrors = true.
After seeing this error, I quickly realized my problem was simply one of mismatched names: My controller is actually named ErrorController with no 's', not ErrorsController. The problem for me was that I had set routeData.Values["controller"] = "Errors";, which is wrong. Switching it to routeData.Values["controller"] = "Error"; fixed the problem.
Note that you won't catch the error right away, because you directly instantiate the controller, and it won't compile if you have that part spelled wrong. But inside the controller, calling View() will look for the view using the RouteData instance we constructed and passed to the RequestContext object. So if the controller name is spelled wrong there, MVC doesn't know where to look for the view, and since IIS custom errors are skipped, it fails silently.
Long story short: Check your controller and view names. I assume something similar would happen if you have the controller name correct, but the file name of the view doesn't match.

Please check it out this. It is a best way to implement exception handling in mvc.
I have implemented same exception handling but I am facing some issue but still you can refer this.

Related

Sending new parameters in MvxViewModelRequest from a IMvxNavigationFacade when deeplinking

I am using deeplinking in my app and Im looking to preset some parameters when navigating to the viewmodel using a IMvxNavigationFacade. The deep link url is like this:
myappname://deeplink/toviewwithdata/?navigatetoview=viewtype1&id=78910
So the deep linking is working and im getting to the navigation facade using the assembly attribute
[assembly: MvxNavigation(typeof(RoutingFacade), #"myappname://deeplink/toviewwithdata/\?navigatetoview=(?<viewtype>viewtype1)&id=(?<id>\d{5})")]
I tried to add other parameters to the MvxViewModelRequest using a MvxBundle but dont think im doing it right. here is my navigation facade:
public class RoutingFacade : IMvxNavigationFacade
{
public Task<MvxViewModelRequest> BuildViewModelRequest(string url, IDictionary<string, string> currentParameters)
{
var viewModelType = typeof(FirstViewModel);
var parameters = new MvxBundle();
try
{
// TODO: Update this to handle different view types and add error handling
if (currentParameters != null)
{
Debug.WriteLine($"RoutingFacade - {currentParameters["viewtype"]}, {currentParameters["id"]}");
switch (currentParameters["viewtype"])
{
case "viewtype1":
viewModelType = typeof(FirstViewModel);
parameters.Data.Add("test", "somevalue");
break;
default:
case "viewtype2":
viewModelType = typeof(FirstViewModel);
break;
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"RoutingFacade - Exception: {ex.Message}");
//TODO viewModelType = typeof(ErrorViewModel);
}
return Task.FromResult(new MvxViewModelRequest(viewModelType, parameters, null));
}
then my viewmodel Init method
public void Init(string id, string viewtype, string test)
{
// Do stuff with parameters
}
but the test parameter is null? How do you pass parameters into a MvxViewModelRequest?
Update:
Don’t know if its possible from looking at the source here https://github.com/MvvmCross/MvvmCross/blob/f4b2a7241054ac288a391c4c7b7a7342852e1e19/MvvmCross/Core/Core/Navigation/MvxNavigationService.cs#L122 as the request parameters get set from the regex of the deeplink url and the return from BuildViewModelRequest, facadeRequest.parameterValues get ignored.
Added this functionality in this pull request

Extension methods cannot be dynamically dispatched error - how do I solve this?

Can't find the proper solution to this problem.
I am using [Serializable] (MVC3 Futures) in order to have a "wizard" with separate views. Here is the code in my controller to serialize:
private MyViewModel myData;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var serialized = Request.Form["myData"];
if (serialized != null) //Form was posted containing serialized data
{
myData = (MyViewModel)new MvcSerializer().Deserialize(serialized, SerializationMode.Signed);
TryUpdateModel(myData);
}
else
myData = (MyViewModel)TempData["myData"] ?? new MyViewModel();
TempData.Keep();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Result is RedirectToRouteResult)
TempData["myData"] = myData;
}
Further along in my controller I do something like this (just a snippet - code goes through wizard with next and back button strings):
public ActionResult Confirm(string backButton, string nextButton)
{
if (backButton != null)
return RedirectToAction("Details");
else if ((nextButton != null) && ModelState.IsValid)
return RedirectToAction("Submitted");
else
return View(myData);
}
In my .cshtml view, I have this:
#using (Html.BeginFormAntiForgeryPost())
{
#Html.Hidden("myData", new MvcSerializer().Serialize(Model, SerializationMode.Signed))
...
#Html.TextBoxFor(m => model.Step.EMail)
...
}
Because I am using dynamics, I have to use a variable instead in the view:
var model = (MyViewModel) Model.myData;
in order to do the #Html.TextBoxFor above. And herein lies my probelm, because if I do #model MyViewModel instead, then I can't do model.Step.EMail. But because of dynamics, the #Html.Hidden won't work and I get the following error:
Compiler Error Message: CS1973: 'System.Web.Mvc.HtmlHelper'
has no applicable method named 'Hidden' but appears to have an
extension method by that name. Extension methods cannot be dynamically
dispatched. Consider casting the dynamic arguments or calling the
extension method without the extension method syntax.
I can switch to some other way of doing this without [Serializable], but then I have to convert a LOT of code. Is there any way to make this work?
The extension method is not identifying the method because, the data type does not match.
Try cast as object.
#Html.Hidden("myData", new MvcSerializer().Serialize(Model, SerializationMode.Signed) as Object)
or
#Html.Hidden("myData", (Object)new MvcSerializer().Serialize(Model, SerializationMode.Signed))
It will works.
You can call
#(InputExtensions.Hidden(Html, "myData", new MvcSerializer().Serialize(Model, SerializationMode.Signed)))
instead of #Html.Hidden(...)
It is calling the extension method without the extension method syntax.

Checking to see if ViewBag has a property or not, to conditionally inject JavaScript

Consider this simple controller:
Porduct product = new Product(){
// Creating a product object;
};
try
{
productManager.SaveProduct(product);
return RedirectToAction("List");
}
catch (Exception ex)
{
ViewBag.ErrorMessage = ex.Message;
return View("Create", product);
}
Now, in my Create view, I want to check ViewBag object, to see if it has Error property or not. If it has the error property, I need to inject some JavaScript into the page, to show the error message to my user.
I created an extension method to check this:
public static bool Has (this object obj, string propertyName)
{
Type type = obj.GetType();
return type.GetProperty(propertyName) != null;
}
Then, in the Create view, I wrote this line of code:
#if (ViewBag.Has("Error"))
{
// Injecting JavaScript here
}
However, I get this error:
Cannot perform runtime binding on a null reference
Any idea?
#if (ViewBag.Error!=null)
{
// Injecting JavaScript here
}
Your code doesnt work because ViewBag is a dynamic object not a 'real' type.
the following code should work:
public static bool Has (this object obj, string propertyName)
{
var dynamic = obj as DynamicObject;
if(dynamic == null) return false;
return dynamic.GetDynamicMemberNames().Contains(propertyName);
}
Instead of using the ViewBag, use ViewData so you can check for the of the item you are storing. The ViewData object is used as Dictionary of objects that you can reference by key, it is not a dynamic as the ViewBag.
// Set the [ViewData][1] in the controller
ViewData["hideSearchForm"] = true;
// Use the condition in the view
if(Convert.ToBoolean(ViewData["hideSearchForm"])
hideSearchForm();
I would avoid ViewBag here completely.
See my thoughts here on this:
http://completedevelopment.blogspot.com/2011/12/stop-using-viewbag-in-most-places.html
The alternative would be to throw a custom error and catch it. how do you know if the database is down, or if its a business logic save error? in the example above you just catch a single exception, generally there is a better way to catch each exception type, and then a general exception handler for the truly unhandled exceptions such as the built in custom error pages or using ELMAH.
So above, I would instead
ModelState.AddModelError()
You can then look at these errors (assuming you arent jsut going to use the built in validation) via
How do I access the ModelState from within my View (aspx page)?
So please carefully consider displaying a message when you catch 'any' exception.
You can use ViewData.ContainsKey("yourkey").
In controller:
ViewBag.IsExist = true;
In view:
if(ViewData.ContainsKey("IsExist")) {...}
I need to test this but:
#if (ViewBag.ABoolParam ?? false)
{
//do stuff
}
I think will give you either the value of the ViewBag property, or return a default value if missing.

RedirectToAction not working as expected

I have a simple MVC3 application that I want to retrieve some configuration details from a service, allow the user to edit and save the configuration.
If any errors are detected during the saving process, these are to be returned and reported back to the user.
The problem is that the configuration containing the errors is failing to be called and the currently saved values are just being redisplayed.
Stepping through the code, when errors are detected, it should redirect to itself using the passed config object but it doesn't and uses the method with no parameter.
Can anyone see where I'm going wrong?
Below are the two controller methods that are being called:
//
// GET: /Settings/Edit/
public ActionResult Edit()
{
SettingsViewModel config = null;
// Set up a channel factory to use the webHTTPBinding
using (WebChannelFactory<IChangeService> serviceChannel =
new WebChannelFactory<IChangeService>(new Uri(baseServiceUrl)))
{
// Retrieve the current configuration from the service for editing
IChangeService channel = serviceChannel.CreateChannel();
config = channel.GetSysConfig();
}
ViewBag.Message = "Service Configuration";
return View(config);
}
//
// POST: /Settings/Edit/
[HttpPost]
public ActionResult Edit( SettingsViewModel config)
{
try
{
if (ModelState.IsValid)
{
// Set up a channel factory to use the webHTTPBinding
using (WebChannelFactory<IChangeService> serviceChannel = new WebChannelFactory<IChangeService>(new Uri(baseServiceUrl)))
{
IChangeService channel = serviceChannel.CreateChannel();
config = channel.SetSysConfig(config);
// Check for any errors returned by the service
if (config.ConfigErrors != null && config.ConfigErrors.Count > 0)
{
// Force the redisplay of the page displaying the errors at the top
return RedirectToAction("Edit", config);
}
}
}
return RedirectToAction("Index", config);
}
catch
{
return View();
}
}
return RedirectToAction("Index", config);
You cannot pass complex objects like this when redirecting. You will need to pass query string parameters one by one:
return RedirectToAction("Index", new {
Prop1 = config.Prop1,
Prop2 = config.Prop2,
...
});
Also I couldn't see an Index action in your controller. Maybe it's a typo. Another thing I notice is that you have an Edit GET action to which you are probably trying to redirect but this Edit action doesn't take any parameters so it just seems weird. If you are trying to redirect to the POST Edit action, well, that's obviously impossible since a redirect is always on GET by its very nature.

Return an other action result as string

In my MVC website, I am creating a small forum. For a single post I am rendering my "Single(Post post)" action in my "PostController" like below
<% Html.RenderAction<PostController>(p => p.Single(comment)); %>
Also When a user reply a post I am sending reply as an ajax request to my "CreatePost" action then return "Single" view as result of this action like below
public ActionResult CreatePostForForum(Post post)
{
//Saving post to DB
return View("Single", postViewData);
}
When I do like that only the view is being rendered, Codes in "Single" Actions body isn't beig executed.
What is the best way to do this?
Also I want to return "Single" action result as string in my JsonObject like below
return Json(new{IsSuccess = true; Content= /*HERE I NEED Single actions result*/});
You can use something like this, but be very careful with this. It can actually cause badly traceable errors (for example when you forget to explicitly set view name in Single method).
public ActionResult Single(PostModel model) {
// it is important to explicitly define which view we should use
return View("Single", model);
}
public ActionResult Create(PostModel model) {
// .. save to database ..
return Single(model);
}
Cleaner solution would be to do the same as if it was post from standard form - redirect (XMLHttpRequest will follow it)
For returning ajax views wrapped in json I use following class
public class AjaxViewResult : ViewResult
{
public AjaxViewResult()
{
}
public override void ExecuteResult(ControllerContext context)
{
if (!context.HttpContext.Request.IsAjaxRequest())
{
base.ExecuteResult(context);
return;
}
var response = context.HttpContext.Response;
response.ContentType = "application/json";
using (var writer = new StringWriter())
{
var oldWriter = response.Output;
response.Output = writer;
try
{
base.ExecuteResult(context);
}
finally
{
response.Output = oldWriter;
}
JavaScriptSerializer serializer = new JavaScriptSerializer();
response.Write(serializer.Serialize(new
{
action = "replace",
html = writer.ToString()
}));
}
}
}
It is probably not the best solution, but it works quite well. Note that you will need to manually set View, ViewData.Model, ViewData, MasterName and TempData properties.
My recommendation:
Post your forum reply (and whatever options) via Ajax.
Return your JSONResult, using this method: ASP MVC View Content as JSON to render your content.
In the OnSuccess handler of your ajax call, check if IsSuccess is true. If successful, append the content to the appropriate container using JQuery

Resources