This is what I'm trying to achieve:
I want to extend the HandleErrorAttribute so I can redirect to an appropriate error page. I had a look at the implementation of HandleErrorAttribute and noticed that essentially it just returns a ViewResult, that points to a view, that can be defined per Exception type.
I'd like to use the same system as HandleErrorAttribute, except:
I'd like to define a Controller for Error pages
I want to maintain the Exception as model (HandleErrorInfo)
I could obviously use a RedirectToRouteResult, but I can't pass through the Exception as model, since it's an actual Response.Redirect. And I'd like to avoid having to cache the model server side.
For now I'm just overwriting the ViewResult and manually setting controller. But that still just returns the view and doesn't actually execute the controller.
public class ErrorViewResult : ViewResult
{
public ControllerBase Controller { get; set; }
public string ControllerName { get; set; }
protected override ViewEngineResult FindView(ControllerContext context)
{
context.Controller = Controller;
context.RouteData.Values["controller"] = ControllerName;
return base.FindView(context);
}
}
I have to somehow return a result, that restarts the whole pipeline starting with the Controller.
Any ideas?
Thanks!
Be careful with TempData functionality, it will store your values only till the next request, and if in between these requests you will do any others or if you use mvc to handle client resources (like dynamically combined css and js files) then you will loose your data.
If you want to start controller manually (with all nested actions) then look at that:
RouteData data = new RouteData();
data.Values.Add("controller", "error");
data.Values.Add("action", "Handle500");
data.Values.Add("area", "");
data.Values.Add("exception", sb.ToString());
var controller = new MTool.BusinessLogic.Controllers.ErrorController();
controller.ControllerContext = new ControllerContext([HttpContextInstance], data, controller);
controller.ActionInvoker.InvokeAction(controller.ControllerContext, "Handle500");
Why not use TempData?
TempData allows you to store data that can be read on the very next request. If they refresh the page after being redirected it will be gone.
TempData["exception"] = exception;
return RedirectToAction("Index");
Now in the get you have access to TempData["exception"] for the first GET only, then it is gone. Sounds like what you need.
Related
I have an old MVC3 site where I was able to get the area of a route by using the following code:
object oArea;
RouteData.DataTokens.TryGetValue("area", out aArea);
I am creating a new MVC5 application and have started to use attribute based routing as follows:
[RouteArea("Area")]
[RoutePrefix("Test")]
public TestController
{
public ActionResult Index()
{
return View("Index");
}
}
Unfortunately, it appears that when you use attribute based routing then the RouteData.DataTokens collection is empty. The area information appears buried under the RouteData in "MS_DirectRouteMatches", so you could get the data as follows:
RouteData.Values["MS_DirectRouteMatches"])[0].DataTokens.TryGetValue("area", out oArea);
However, I was wondering if there is an easier, safer or better way to get the area data in MVC5. The area name is actually the sub-tool name within a larger application, which is used for some logic in the base controller initialization.
The only "safe" way is to first check for the existence of the MS_DirectRouteMatches and only probe for the area if it exists, falling back to the original RouteData object if it does not.
string area;
RouteData routeData = HttpContext.Request.RequestContext.RouteData;
if (routeData != null)
{
if (routeData.Values.ContainsKey("MS_DirectRouteMatches"))
{
routeData = ((IEnumerable<RouteData>)routeData.Values["MS_DirectRouteMatches"]).First();
}
routeData.DataTokens.TryGetValue("area", out area);
}
Situation:
I have a controller (with its associated views), that after it was developed, it was decided that it will be excluded from the current phase, and instead, it will be included in some phase in the future.
I cannot simply exclude or delete the controller and the views from the solution because of what I mentioned that will be using the controller in the future. Something that came to my mind was to force each action on the controller to redirect to the main page if they are accessed, but I think this is not too "elegant".
Question:
What other method can I use to block the execution of an action in a controller when they are accessed through the URL?
Keeping unused code in your codebase like that is generally a bad idea. Assuming you have some sort of version control in place, keep a branch around with that controller, but delete it from your master branch. Merge that branch back in when you're bringing the feature back.
If, however, removing the code really isn't possible, I'd make a custom filter to redirect any requests to that controller to some other URL.
i.e.
[RedirectTo("Index", "Home")]
public class MyFutureController : Controller {
...
where you have
public class RedirectToFilter : ActionFilterAttribute {
public string RedirectAction { get; set;}
public string RedirectController { get; set; }
public RedirectToFilter(string redirectAction, string redirectController) {
RedirectAction = redirectAction;
RedirectController = redirectController;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = RedirectToAction(RedirectAction, RedirectController);
}
}
Any requests to FutureController will just get redirected to your Home/Index route. In the future, you just have to remove the attribute from the controller and you're good to go.
You can add a custom route, so that when request comes for this controller it actually redirects to some other controller, e.g. HomeController
Use an OnActionExecuting action filter on the controller.
public class FooController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Detail()
{
return View();
}
protected override void OnActionExecuting( ActionExecutingContext filterContext )
{
filterContext.Result = RedirectToAction( "Index", "Home" );
}
When you're ready to start using the controller just delete the OnActionExecuting method and you're all set.
Just decorate the action method with the [NonAction] attribute. More Here
You could simply write a view to display to the user who directs themselves there that it is still under construction or something of the like. Excluding or commenting out the controller is certainly an option, or you could rewrite all associated methods to reroute to another page.
I think the best solution is probably to just exclude it altogether, having it in your application doesn't benefit you or the user if you're not using it currently. The code should remain so you're free to tweak and refine it before you actually get a release version going.
All,
I have an MVC _Layout.cshtml that calls:
#Html.Action("GetActionStrip", "Vehicles")
I also have a Controller that loads some views.
My Issue is that I have a controller action called GetVehicleDetails which gets a vehicle by ID.
The Action on:
#Html.Action("GetActionStrip", "Vehicles")
requires that GetVehicleDetails loads first as it need to put a vehicle id into session.
This is not working as:
#Html.Action("GetActionStrip", "Vehicles")
Loads before GetVehicleDetails.
#Html.Action("GetActionStrip", "Vehicles")
Needs to be on multiple views, that's why I put it in the _Layout file.
I can get it to work by putting:
#Html.Action("GetActionStrip", "Vehicles")
On every view I need it on and then they load in the correct order. ie.. the controller action GetVehicleDetails sets the vehicle id into session and then:
#Html.Action("GetActionStrip", "Vehicles")
Reads the session value.
Has anyone got any idea if I can do it the way I want or will I have to put my #Html.Action on every view which kind of breaks the DRY principle.
thanks
RuSs
Paul, I tried to write you a comment but the character limit killed me. Here is my comment:
Paul,
Thanks for the message. I understand what you have written but before I continue and try to implement something like this I just want to be sure you understand, fully, the scenario.
Will YOUR scenario cater for the fact that the code that needs the session value is called from an #Html.Action in my _Layout (master page so to speak) whereas I need my controller get action to receive a parameter and set this parameter into session.
From what I understand, _Layouts (master pages) load first so my #Html.Action would run and look for the session value. But, as this code is in a _Layout, it would run first and hence the GET on my controller has not yet set the session from the actions passed in parameter.
Note: my _Layout doesnt have it's own controller (not sure if this matters)
Thanks
RuSs
Something about the design is fundamentally incorrect. You shouldn't have different components being tightly coupled like this. The order shouldn't matter for which one comes first.
Here is how I might do what you're looking for. I'd create a model bound class that you can receive in your controller actions where you need the session value. The model will pull the session value from the database or wherever if it hasn't been set yet otherwise it uses the session value. Now order doesn't matter. Better yet you could make MySessionObject an interface and then you can mock it out in your test cases.
public interface IMySessionObject
{
int GetValueX();
}
public class MySessionObject : IModelBinder, IMySessionObject
{
private HttpContextBase _httpContext;
private MySessionObject(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
public int GetValueX()
{
if (_httpContext.Session["x"] == null)
{
_httpContext.Session["x"] = 54; // Get the value here.
}
return (int)_httpContext.Session["x"];
}
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var context = controllerContext.HttpContext;
var obj = new MySessionObject(context);
return obj;
}
}
public class HomeController : Controller
{
public ActionResult Index(IMySessionObject obj)
{
ViewBag.X = obj.GetValueX();
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
public ActionResult About(IMySessionObject obj)
{
ViewBag.Message = "Your app description page.";
return View();
}
public ActionResult Contact(IMySessionObject obj)
{
ViewBag.Message = "Your contact page.";
return View();
}
}
Thanks Paul. I think your answer is correct without Sitecore CMS being part if the scenario. Ill vote you up but still doesn't fix my issue. Here is my other post which explains it better.
I don't expect you use Sitecore. https://stackoverflow.com/questions/14867915/order-of-loading-layout-and-url-driven-action-is-opposite-to-a-standard-non-sit ill try to find a way for Sitecore NOT to declaratively load my _layout before my MVC code runs.
I am trying to insert a custom header (held in a database) on my master page. The custom header changes on a user by user basis.
I'm slightly confused on how to attach a controller to a partial (if I even can do this). What I'm hoping to accomplish is to render a block of code from a specific controller called with some events.
public ActionResult GetHeader(Guid clientID)
{
string szHeader = GetTheme(ThemeType.Portal, JoloTheme.ThemeArea.Header, clientID);
return Content(szHeader, "text/html");
}
Is the controller I have created, but I'm not sure how to get this onto a subsection of a page I am currently writing (not in the same Controller).
Apologies if this is completely nonsense here, still learning MVC I'm afraid.
You should use Html.Action() helper in your _Layout.cshtml. I think you should use a Nullable Guid as a parameter because you won't be able to provide a valid clientID always.
public ActionResult GetHeader(Guid? clientID)
{
string szHeader = GetTheme(ThemeType.Portal, JoloTheme.ThemeArea.Header, clientID);
return Content(szHeader, "text/html");
}
Here's how you should call Html.Action helper in your _Layout.cshtml
#Html.Action("GetHeader",
"SomeController",
new { clientID = IsLoggedIn ? ClientID : (Guid?)null } )
So I have an MVC app that should change the Website title, and header color based on the domain the app is hit from. So I have a simple table setup in SQL as such:
DomainName (PK), WebsiteTitle, HeaderColor
Domain1.com, Website Title for Domain 1, #ebebeb
So I am trying to figure out the best way to return this information for each page view. Sure I can go ahead and lookup the site info in each model thats returned from the controller. But are there any other ways I can approach this? Maybe at a lower level in the stack?
Thank you!
There are many ways you can do this. ActionFilters are one way, or in a BaseController.
You need to determine if every action requires this, or if only certain actions.
If you decide every action, create a controller base, inheriting from Controller, then overriding OnActionExecuting. In that method you can make you calls to fetch and add the data to viewdata. Like so:
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewData.Add("SiteTitle", "Site title");
base.OnActionExecuting(filterContext);
}
}
If you prefer to use a base viewmodel that has this information, it would be best to override OnActionExectued where you can get access to the actions results, and modify the base model to set your values. Like so:
public class BaseController : Controller
{
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as ViewResultBase;
var baseModel = (BaseViewModel) result.ViewData.Model;
baseModel.SiteTitle = "Site Title";
base.OnActionExecuted(filterContext);
}
}
Depending if you want an inheritence chain for your viewmodels. Either works. You'll also notice that I just set the values. Use whatever source for values you need. If you are pulling them from the db, I would cache the values so that for every action you are not hitting the db for it.
This problem is fundamentally identical to swapping layout or master pages for mobile vs desktop browsers. However, instead of looking at the device caps in a web request to determine which layout to use, you'd check the domain of the request.
See this article for a slightly complex (but thorough) overview of selecting mobile vs desktop views. Much of what the author says is focused on detecting screen solution, etc., which doesn't directly apply to you, but the mechanism for selecting the master or layout page should be just what you're looking for.
Or, you can handle this through inheritance.
Implement a base controller, like so:
public class BaseController : Controller
{
public string SiteTitle { get { .... } }
public string HeaderColor { get { ... } }
/// whatever other "global" properties you need
}
Then, each of your controllers inherit from BaseController
public class HomeController : BaseController
{
public ActionResult Index()
{
var myTitle = SiteTitle;
/// then, do whatever you want with it
return View();
}
}
In the property accessors in BaseController, read the title and whatever other properties you need from a .settings file or the AppSettings section in web.config.
Controller also provides events that can be used to set these properties so that you don't have to duplicate any code for getting those values into each view.