Inject javascript on every controller action - asp.net mvc - asp.net-mvc

Whenever we make a call to any action controller whether through a full post back or an ajax call I want to check the view model and depending on some logic inject some javascript to be executed in the browser. The application is mostly already coded and what I am trying to do is to have some generic code which can do the trick. I am wondering what would be the best way to do it.
I am thinking about using action filters and then checking the model and then inject the js if required. But not sure how would that work on events like action executed etc. Any code sample will be helpful.
The other option is to do it on the client side. But again not sure how to properly do it in a generic way.

Look into overriding the base controller's OnActionExecuted event, which should provide you access to the view model after it has been processed by the action. I'm curious though, how exactly are you going to inject a snippet of javascript into an AJAX response, which is typically a simple JSON object?
If all you're really asking is how to inject javascript from the controller, you could put the following in your view:
<script type="text/javascript" defer="defer">
#Html.Raw(ViewBag.StartupScript)
</script>
You could add the above to a specific view, or a layout page. Then, you could do something like this:
public class MyController : Controller
{
public override void OnActionExecuted(...)
{
if (...)
{
ViewBag.StartupScript = "alert('hello world!');";
}
}
}

To inject on postback as well as ajax calls here is how you can do it:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
StringBuilder sb = new StringBuilder();
sb.Append("<script type=\"text/javascript\">\n\t");
sb.Append("alert('Hello Injection');");
sb.Append("</script>\n");
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.Write(sb.ToString());
}
else
{
ViewBag.StartupScript = sb.ToString();
}
}
Probably not the cleanest solution, but works.

filterContext.HttpContext.Response.Write(sb.ToString());
this will override the partial view come from Ajax call, any walk around?

Related

Embedded code in Razor _Layout.cshtml

I'm working on an MVC3 Razor web application which gets it's page decoration from a java content management system. As this decoration is shared by every page I've put the retrieval of the CMS content in the _Layout.cshtml file but I'm not entirely happy with the code I've implemented...
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
#{
-- The first two lines are temporary and will be removed soon.
var identity = new GenericIdentity("", "", true);
var principal = new GenericPrincipal(identity, new string[] { });
var cmsInterface = MvcApplication.WindsorContainer.Resolve<ICMSInterface>();
cmsInterface.LoadContent(principal, 2);
}
#Html.Raw(cmsInterface.GetHeadSection())
</head>
<body>
#Html.Raw(cmsInterface.GetBodySection(0))
#RenderBody()
#Html.Raw(cmsInterface.GetBodySection(1))
</body>
</html>
As there is no controller for the _layout file I can't see where else I could put the code to do the retrieval. Here are a few things that I've considered:
Retrieve the CMS content in separate pieces so I don't need the LoadContent call. Unfortunately, because of the component I have to use to retrieve the CMS content this isn't possible, it is all or nothing.
Use a partial view so I can utilise a controller. As I'd need to put the entire page into the partial that option just seems a bit ridiculous.
Call a single static method on some helper class which retrieves the data and adds the three sections to the ViewBag. That will allow me to move the code out of the view and feels like the best solution but I'm still not particularly happy with it.
Does anyone have any other suggestions/comments?
You can use a global action filter to add the required data to the ViewBag in all controllers:
public class LoadCmsAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
if (!filterContext.IsChildAction &&
!filterContext.HttpContext.Request.IsAjaxRequest() &&
filterContext.Result is ViewResult)
{
var identity = new GenericIdentity("", "", true);
var principal = new GenericPrincipal(identity, new string[] { });
var cmsInterface = MvcApp.WindsorContainer.Resolve<ICMSInterface>();
cmsInterface.LoadContent(principal, 2);
var viewBag = filterContext.Controller.ViewBag;
viewBag.HeadSection = cmsInterface.GetHeadSection();
viewBag.FirstBodySection = cmsInterface.BodySection(0);
viewBag.SecondBodySection = cmsInterface.BodySection(1);
}
}
}
Global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
...
filters.Add(new LoadCmsAttribute());
}
One solution:
Create a base controller that each of your controllers inherits from.
Override OnActionExecuted or a similar overview
Add the data to the ViewBog or ViewData in your overridden code
The OnActionExecuted code will be run every time an action is called so you may want to perform some checking to ensure that the action will return a view, etc. There may be a better override OnActionExecuting, OnResultExecuting, etc. but that's the one that comes to the top of my mind.
Another Solution:
Create a filter attribute that you can use to decorate you controllers.
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/understanding-action-filters-cs
This might be a better solution if you don't want a base controller.
*"As there is no controller for the _layout file".*
The point is that your assumption is not entirely true! As a matter of fact it is quite common to populate the _Layout page with the help of controllers. Let think for instance to the Login/Logout rectangle that is a lot of web sites...it is typically put in the _Layout and handled with a controller.
There is no controller that pass a ViewModel to the Layout...simply because the Layout...is just...a Layout and not something that convey information...howeber it can be a "container" for other contents that in turn may have a ViewModel.
In practice you can invoke Child controllers from the _Layout by using Html.Action or Html.RenderAction...This is the way Login is handled in most of the asp.net Mvc web site...and I suggest you to do the same for your content...fill your content by invoking specialized child controllers, one for each different "area" of the Layout page.
Seems like your answer might be here: child action that obviates need for base controller.

How do I access a ViewBag.Title after it has been set by the underlying View?

Here's the thing. I have a MVC Action, and on that action, I have applied a custom ActionFilterAttribute to get the deserialization working. Now, what I want to do, is set some header based on the ViewBag.Title that is set inside this view.
I've tried wrapping the ViewResult in my own, and overriding the ExecuteResult but the ViewBag is always empty :-(.
Is this even possible or does the MVC engine reset the ViewBag once the _layout is executed?
Update:
Let me post some code samples, to make clearer what I want to do. I have an email service, where I render the body from a MVC view. So my view looks like this:
#{ViewBag.Title = "EventCreated";}
Something that ressembles an email message here.
Now I have a Controller, with an action that looks something like this:
public ActionResult HelloWorld(MailView<HelloWorldMessage> msg)
{
Response.Headers["subject"] = "Test subject";
return View(msg);
}
I want to make that Headers["subject"] statement to look like Response.Headers["subject"] = ViewBag.Title; and I want to do be able to let the View think it's handling a normal web page.
I've tried using an ActionFilterAttribute and overriding OnResultExecuted but couldn't get it to work.
One possible option is to set it on the layout page and actually decide based on certain criteria which layout to use. That way I can still keep the Reponse thing away from my views but make it rather clean. What do you think?
Thanks,
Anže
Try this - assign the output of the view result
var output = View(msg);
//do your other viewbag stuff here
return output;
Why all of this though - I didn't follow when you said "and I want to do be able to let the View think it's handling a normal web page."
Edit:
Why don't you then just set this via a helper method in your View?
ala
#{
SetTitle("Home Page");
}
and
#functions {
public void SetTitle(string title)
{
ViewBag.Title = title;
Response.Headers.Add("title", title);
}
}

determine when a partialview is going to be rendered as fullview... and hijack the rendering - MVC

So I have this page like this:
....
now I am updating this Comments div using Ajax and all. Now, if the user has javascript disabled, the actionresult "Comments" still ends up returning the partialView except this time it replaces the entire page instead of just div "CommentsDiv". It ruins the formatting of the page, because masterpage is gone. There are a lot of such scenarios throughout the website.
Can I universally specify something like if a partialView is about to be rendered as full view, do something!! (like maybe redirect to a dummy full-page with masterpage only referencing the partialview). Any other approaches?
Note that I simply can't do "IsAjaxRequest",because the very first time the page loads, it won't be an Ajax request, but the actionresult is still supposed to return partialview.
If I have understood your comment about IsAjaxRequest, the first time the page loads, you want the full view, not the partial... But that is pretty much the canonical reason for using IsAjaxRequest.
So all you would need is:
if (Request.IsAjaxRequest)
{
return View();
}
else
{
return PartialView("myPartial");
}
The only other scenario I can think of is where you are using a redirect, eg, if implementing the Post Redirect Get pattern. In that case, you can override the OnResultExecuted method in your controller to store the result of IsAjaxRequest in TempData.
That way, when the Get request hits the server, you can check your variable in TempData. If it is empty, then it is an "original" request, so return the full page. Else, it is a redirected request and the original request WAS an Ajax request, and you can return the partial view safely. Ie:
Write a property in your controller as follows:
public bool ImReallyAnAjaxRequest
{
get
{
if (TempData["ImAjax"] == null) return false;
if (TempData.ContainsKey("ImAjax"))
{
return (bool)TempData["ImAjax"];
}
else if (Request.IsAjaxRequest())
{
return true;
}
else
{
return false;
}
}
}
Then, write the OnResultExecuted as follows:
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Result is RedirectToRouteResult)
{
TempData[keyIsAjaxRequest] = Request.IsAjaxRequest();
}
}
That way you cover all angles and you can just use the ImReallyAnAjaxRequest everywhere else and know that it will work, whatever the scenario. Or rather, I have used this to build a base WizardController that is completely transparent to Ajax being available or not. A thing of beauty and very dry, especially if packaged into a base controller.
However, I speculate, as your question is not clear.

Using a filter to execute a different action?

I want to avoid having lots of if Request.IsAjaxRequest() in my controllers. I was thinking that if I could condense this logic to an ActionFilter it would be easy to adopt a convention in my application to provide a second action for any request that may use Ajax, while providing a fall back if JavaScript is disabled.
public ActionResult Details(int id)
{
// called normally, show full page
}
public ActionResult Details_Ajax(int id)
{
// called through ajax, return a partial view
}
I initially thought I could do something like this:
public class AjaxRenameAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.RouteData.Values["action"] = filterContext.RouteData.Values["action"] + "_Ajax";
}
But that won't work because the action to invoke is decided and then the Filters on it are processed.
I don't really want to return a RedirectResult each time someone calls an action, it seems a bit pointless to double the amount of Http requests.
Is there a different way to route through the request to a different action? Or is what I'm doing unadvisable and I should look for a better way of doing things?
Cheers
How about AcceptAjaxAttribute in MvcFutures?
AcceptAjaxAttribute is probably is what's needed here; however, I'd like to propose a different way of thinking about this problem.
Not all Ajax requests are equal. An Ajax request could be trying to accomplish any of the following things:
Binding JSON data to a rich grid (like jqGrid);
Parsing/transforming XML data, such as an RSS feed;
Loading partial HTML into an area of the page;
Asynchronously loading a script (google.load can do this);
Handling a one-way message from the client;
And probably a few more that I'm forgetting.
When you "select" a specific "alternate action" based solely on the IsAjaxRequest method, you are tying something very generic - an asynchronous request - to specific functionality on the server. It's ultimately going to make your design more brittle and also make your controller harder to unit test (although there are ways to do it, you can mock the context).
A well-designed action should be consistent, it should only care what the request was for and not how the request was made. One might point to other attributes like AuthorizeAttribute as exceptions, but I would make a distinction for filters, which most of the time describe behaviour that has to happen either a "before" or "after" the action takes place, not "instead of."
Getting to the point here, the goal stated in the question is a good one; you should definitely have different methods for what are correctly described as different actions:
public ActionResult Details(int id)
{
return View("Details", GetDetails(id));
}
public ActionResult JsonDetails(int id)
{
return Json(GetDetails(id));
}
public ActionResult PartialDetails(int id)
{
return PartialView("DetailTable", GetDetails(id));
}
And so on. However, using an Ajax action selector to choose between these methods is following the practice of "graceful degradation", which has essentially been superseded (at least IMO) by progressive enhancement.
This is why, although I love ASP.NET MVC, I mostly eschew the AjaxHelper, because I don't find that it expresses this concept that well; it tries to hide too much from you. Instead of having the concept of an "Ajax Form" or an "Ajax Action", let's do away with the distinction and stick to straight HTML, then inject the Ajax functionality separately once we're certain that the client can handle it.
Here's an example in jQuery - although you can do this in MS AJAX too:
$(function() {
$("#showdetails").click(function() {
$("#details").load("PartialDetails", { id: <%= Record.ID %> });
return false;
}
});
This is all it takes to inject Ajax into an MVC page. Start with a plain old HTML link and override it with an Ajax call that goes to a different controller action.
Now, if on somewhere else on your site you decide you want to use a grid instead, but don't want to break the pages using partial rendering, you can write something like this (let's say you have a single master-detail page with a list of "orders" on the left side and a detail table on the right):
$(".detaillink").click(function() {
$('#detailGrid').setGridParam({
url: $(this).attr("href").replace(/\/order\/details/i,
"/order/jsondetails")
});
$("#detailGrid").trigger("reloadGrid");
});
This approach completely decouples client behaviour from server behaviour. The server is effectively saying to the client: If you want the JSON version, ask for the JSON version, and oh, by the way, here's a script to convert your links if you know how to run it. No action selectors and finagling with method overloads, no special mocking you have to do in order to run a simple test, no confusion over which action does what and when. Just a couple of lines of JavaScript. The controller actions are short and sweet, exactly the way they should be.
This is not the only approach. Obviously, classes such as AcceptAjaxAttribute exist because they expected some developers to use the request-detection method. But after having experimented quite a bit with both, I find this way much easier to reason about and therefore easier to design/code correctly.

Controller equivalent of HttpContext.Current in ASP.NET MVC

I'd like to get access to the current executing Controller so I can offload the return of the appropriate ActionResult onto a helper method. To this end, I'm looking for the equivalent of what I would have thought would be ControllerContext.Current but isn't. Thanks!
Edit for clarification: I've got a generic form control which is JavaScript-based but I'd like to add an option so that it works with noscript. At the moment my Controller sets the ViewData.Model to a JSON-ified Models.FormResponse<T>.
This FormReponse is set up with the status of the post and any error messages that were generated, so I'd like a GetActionResult() method which does the script/noscript check (a hidden form input) and either:
Sets the Model to the JSONed FormResponse and returns a View(), or
Serializes the FormResponse to the Session and returns a Redirect().
As this obviously changes the return value and I don't want to do the check myself every time, I need to call View or Redirect from the FormResponse's GetActionResult method in order to call this as:
return formResponse.GetActionResult();
I know with a more astronautical design this could be made even more robust but as the noscript option is not a major feature at the moment, I just need to get a quick solution working that doesn't break other things.
Update #2
The following, implemented in an ActionResult class, does the job for me. Thanks CVertex!
public override void ExecuteResult(ControllerContext context)
{
if (CMSEnvironment.NoScript)
{
Oracle.Response.Redirect(Oracle.Request.UrlReferrer.ToString(), true);
}
context.Controller.ViewData.Model = _model.ToJSON();
new ViewResult()
{
ViewName = Areas.Site.Helpers.SharedView.Service,
ViewData = context.Controller.ViewData
}.ExecuteResult(context);
}
Statics are bad for testability, and very much discouraged in MVC.
Why do you want to access the current controller and action method?
The best way to do this is to implement your own ActionFilter.
This gives you a means of intercepting requests before or after actions methods execute.
EDIT:
By intercepting the result inside OnActionExecuted of a filter, you can do your noscript/script checks and modify your ViewData accordingly for consumption by the View.
Inside OnActionExecuted, you can also do the noscript check and have complete control over the final ActionResult or the ViewData, as you please.
Or, you can write your own ActionResult that makes all these decisions.
So, your controller action ultimately does
return new MyActionResult(format_and_view_agnostic_model_object);
There doesn't appear to be a way to navigate to the current Controller from a thread. That is you could get the ControllerBuilder and you can get the MvcHttpHandler but neither then lets you access the controller instance that the handler is using.

Resources