What I am trying to achieve:
After each view has finished executing I would like to make a separate http call to an external partner.
I need to pass one of the view's content as body of that http call.
What I have so far:
I have a base controller from which all of my controllers inherit from.
I have found that i can override the onActionExecuted() method of the base controller and write my partner http call code there so that it will be executed after each action.
I have written a custom result after reading the article at Send asp.net mvc action result inside email. which enables me to grab the content of the view. (which is part of another controller that also inherits from base controller).
What I can't figure out:
How do I make a call to the controller action (the one that will render the content for the http calls body) to get the content in my base controller onActionExecuted() method?
anil
This will call a second controller action from the first controller action within the same controller:
public ActionResult FirstAction()
{
// Do FirstAction stuff here.
return this.SecondAction(ArgumentsIfAny);
}
public ActionResult SecondAction()
{
// Do SecondAction stuff here.
return View();
}
Doesn't need to be too complicated. :-)
The idea of most MVC frameworks it to make things more simple. Everything breaks down into a call to a method with certain inputs and with certain return values. In a way, you can accomplish what you want by doing something like this:
class MyController {
public ActionResult Action1() {
// Do stuff 1
}
public ActionResult Action2() {
// Do stuff 2
}
}
You can then refactor a bit:
class MyController {
public ActionResult Action1() {
// Pull stuff out of ViewData
DoStuff1(param1, param2, ...);
}
public ActionResult Action2() {
DoStuff2(param1, param2, ...);
}
public void DoStuff1(/* parameters */) {
// Do stuff 1
}
public void DoStuff2(/* parameters */) {
// Do stuff 2
}
}
Now you can just call DoStuff1() and DoStuff2() directly because they're just methods. You can make them static if possible. Don't forget that you'll likely need to do something about error checking and return types.
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
this.ViewData.Model = GLB_MODEL;
Stream filter = null;
ViewPage viewPage = new ViewPage();
viewPage.ViewContext = new ViewContext(filterContext.Controller.ControllerContext, new WebFormView("~/Views/Customer/EmailView.aspx", ""), this.ViewData, this.TempData);
var response = viewPage.ViewContext.HttpContext.Response;
response.Clear();
var oldFilter = response.Filter;
try
{
filter = new MemoryStream();
response.Filter = filter;
viewPage.ViewContext.View.Render(viewPage.ViewContext, viewPage.ViewContext.HttpContext.Response.Output);
response.Flush();
filter.Position = 0;
var reader = new StreamReader(filter, response.ContentEncoding);
string html = reader.ReadToEnd();
}
finally
{
if (filter != null)
{
filter.Dispose();
}
response.Filter = oldFilter;
}
}
This is modified version of code from Render a view as a string. I didn't want to render the result of the view to the httpcontext response stream.
Related
I'm working with an MVC5 project. In this project I have a few things I always want to do in pretty much every response to the client.
For example, I always want to see if the user is logged in, and if so put the name of the user and their id into a the ViewBag variable for use in the .cshtml file.
I have a base controller which all other controllers inherit from. My first thought was to do these things in the constructor of that controller, but this does not work as the User variable does not exist yet.
Is there another way to do this, without calling a Setup() method in each Action?
Can I listen to some event that fires before an ActionResult is returned and insert my ViewBag data there?
Example of what does not work ;)
[InitializeSimpleMembership]
public class BaseController : Controller
{
protected USDatabase _database = new USDatabase();
public BaseController()
{
if (User.Identity.IsAuthenticated == true)
{
var usr = _database.UserProfiles.Where(x => x.UserName.ToLower() == User.Identity.Name.ToLower()).FirstOrDefault();
if (usr != null)
{
ViewBag.UserName = usr.UserName;
ViewBag.UserId = usr.Id;
}
}
}
}
My solution after reading the ideas in the answers below:
Created an Actionfilter I triggered on the base controller.
public class UserDataFilter : ActionFilterAttribute
{
//OnActionExecuting – This method is called before a controller action is executed.
//OnActionExecuted – This method is called after a controller action is executed.
//OnResultExecuting – This method is called before a controller action result is executed.
//OnResultExecuted – This method is called after a controller action result is executed.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var User = filterContext.HttpContext.User;
if (User.Identity.IsAuthenticated == true)
{
using (var db = new USDatabase()) {
var usr = db.UserProfiles.Where(x => x.UserName.ToLower() == User.Identity.Name.ToLower()).FirstOrDefault();
if (usr != null)
{
var ViewBag = filterContext.Controller.ViewBag;
ViewBag.UserName = usr.UserName;
ViewBag.UserId = usr.Id;
}
}
}
}
}
Base controller now looks like this:
[InitializeSimpleMembership]
[UserDataFilter]
public class BaseController : Controller
{
protected USDatabase _database = new USDatabase();
public BaseController()
{
}
}
And all my other controllers Implement the BaseController.
Yes .. what you need is an Action Filter, action filters are .net attributes inherit from ActionFilterAttribute you can do what you specified using them, here is a link to understanding them and a few basic samples on what you do with them:
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/understanding-action-filters-cs
Take a look at http://msdn.microsoft.com/en-us/library/system.web.mvc.controller(v=vs.98).aspx: THere are various Events you could use, depending on the exact circumstances.
For example: OnActionExecuting, OnActionExecuted
I am creating a single page application. I have an abstract class named ControllerBase, and all of my controllers inherit from it.
I have one controller named 'Application', which contains one action named 'Container'. This view contains the application view.
The way I want it to work is: if the request is AJAX, return the ActionResult from a controller. If the request is not AJAX, call the 'Application' controller and render the action 'Container', sending routing data so that the client takes care of making the AJAX calls to build the page.
Here is the controller base.
public class ControllerBase : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.HttpContext.Request;
// If the request is AJAX, let it go through
if (request.Headers["X-Requested-With"].IsEqualTo("xmlhttprequest"))
{
base.OnActionExecuting(filterContext);
}
// If the request is not AJAX, then the response needs to be modified.
else
{
// This is the controller that contains the single-page application view.
var applicationController = new ApplicationController();
applicationController.ControllerContext =
new ControllerContext(ControllerContext.RequestContext, applicationController);
// Render the single page application and let it take care of the result
filterContext.Result = applicationController
.Container(filterContext.RouteData.Values["Controller"].ToString(),
filterContext.RouteData.Values["Action"].ToString(), null);
}
}
}
Here is the application controller.
public class ApplicationController : ControllerBase
{
public ActionResult Container(string controller, string action, string id)
{
if (controller.IsEqualTo("application")) controller = null;
if (action.IsEqualTo("container")) action = null;
return View(new RequestModel(controller, action, id));
}
}
And here is any other controller where any requests made to it must be caught and rendered through the Application controller if it is not AJAX.
public class BusinessController : ControllerBase
{
public ActionResult Index()
{
return View();
}
}
When I route to the following route: /Business/Index, the OnActionExecuting rules work fine, but the view that comes out doesn't render the 'Container' view, it renders the 'Index' view. I can't figure out what I'm doing wrong.
The reason why the wrong view was being rendered is because the filter context was not pointing to the correct controller and action.
Here is the fixed OnActionExecuting method.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.HttpContext.Request;
// If the request is AJAX, let it go through
if (request.Headers["X-Requested-With"].IsEqualTo("xmlhttprequest"))
{
base.OnActionExecuting(filterContext);
}
// If the request is not AJAX, then the response needs to be modified.
else
{
// Get original values of controller and action data
var controllerName = filterContext.RouteData.Values["Controller"].ToString();
var actionName = filterContext.RouteData.Values["Action"].ToString();
// Set new values so that the view engine knows what view to render
filterContext.RouteData.Values["Controller"] = "Application";
filterContext.RouteData.Values["Action"] = "Container";
// This is the controller that contains the single-page application view.
var applicationController = new ApplicationController();
applicationController.ControllerContext =
new ControllerContext(ControllerContext.RequestContext, applicationController);
// Render the single page application and let it take care of the result
filterContext.Result = applicationController.Container(controllerName, actionName, null);
}
}
Now all of my controllers will route to a single page, and that single page will take care of all requests.
I am relatively new to the MVC way of doing things and have run into a bit of a performance issue due to loading a lot of extraneous data in a base controller. I have read a bit about action filters and was wondering if anyone had advice on how to proceed. Here is my issue:
I have a Controller called RegController that inherits from a base Controller called BaseController. In the onActionExectuting method of the base controller I load several variables, etc. that will be used in a visible page such as viewbag, viewdata, etc. I only need to load this stuff for Actions with the result type ActionResult. In the same RegController I also have some JsonResult Actions that do not need all of this extra info loaded because they do not load a page or view template they only return Json. Does anyone have a suggestion how to handle this properly within existing framework features? Should I be putting these action in different controllers instead? I have many controllers with varying functionality split up in this same way and am hoping I can filter the action type versus moving them all to a JsonResultController or whatever.
Code (some of it pseudo) below:
public class BaseController : Controller
{
protected string RegId;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (filterContext.ActionParameters.ContainsKey("rid") && filterContext.ActionParameters["rid"] != null)
RegId = filterContext.ActionParameters["rid"].ToString();
Reg oReg = new Reg(RegId);
// IF A VISIBLE PAGE IS BEING SERVED UP, I NEED TO LOAD THE VIEWDATA FOR THE PAGE
SetViewAndMiscData(oReg);
// IF THIS IS A JSON RESULT ACTION, I DO NOT WANT TO LOAD THE VIEWDATA FOR THE PAGE
}
}
public class RegController : BaseController
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
public ActionResult Page(string id)
{
// THE VIEW DATA SHOULD BE SET FROM THE BASECONTROLLER FOR USE IN THE PAGE
return View();
}
public JsonResult Save(string id, string data)
{
// I DON"T NEED ALL OF THE VIEW AND MISC DATA LOADED HERE, INCLUDING THE AUTO LOAD
// OF THE REG OBJECT SINCE I DO IT HERE FROM THE PASSED PARAMS.
GenericResponseObject Response = new GenericResponseObject();
Reg oReg = new Reg(id);
if (!oReg.IsValid)
{
Response.Status = 1;
Response.Message = "Invalid Record";
}
else
Response = oReg.SaveData(data);
return Json(Response);
}
}
My project structure is like:
Controllers/ArticlesController.cs
Controllers/CommentsController.cs
Views/Articles/Read.aspx
Read.aspx takes a parameter say "output", which is the details of the article by id and its comments, passed from ArticlesController.cs
Now I want to write then read the comment:: write() & Read() funct in CommentsController.cs
For reading the article with its comments, I want to call Views/Articles/Read.aspx from CommentsController.cs by passing output parameter from CommentsController.cs
How can I do this?
UPDATE
Code Here:
public class CommentsController : AppController
{
public ActionResult write()
{
//some code
commentRepository.Add(comment);
commentRepository.Save();
//works fine till here, Data saved in db
return RedirectToAction("Read", new { article = comment.article_id });
}
public ActionResult Read(int article)
{
ArticleRepository ar = new ArticleRepository();
var output = ar.Find(article);
//Now I want to redirect to Articles/Read.aspx with output parameter.
return View("Articles/Read", new { article = comment.article_id });
}
}
public class ArticlesController : AppController
{
public ActionResult Read(int article)
{
var output = articleRepository.Find(article);
//This Displays article data in Articles/Read.aspx
return View(output);
}
}
To directly answer your question if you want to return a view that belongs to another controller you simply have to specify the name of the view and its folder name.
public class CommentsController : Controller
{
public ActionResult Index()
{
return View("../Articles/Index", model );
}
}
and
public class ArticlesController : Controller
{
public ActionResult Index()
{
return View();
}
}
Also, you're talking about using a read and write method from one controller in another. I think you should directly access those methods through a model rather than calling into another controller as the other controller probably returns html.
You can move you read.aspx view to Shared folder. It is standard way in such circumstances
I'm not really sure if I got your question right. Maybe something like
public class CommentsController : Controller
{
[HttpPost]
public ActionResult WriteComment(CommentModel comment)
{
// Do the basic model validation and other stuff
try
{
if (ModelState.IsValid )
{
// Insert the model to database like:
db.Comments.Add(comment);
db.SaveChanges();
// Pass the comment's article id to the read action
return RedirectToAction("Read", "Articles", new {id = comment.ArticleID});
}
}
catch ( Exception e )
{
throw e;
}
// Something went wrong
return View(comment);
}
}
public class ArticlesController : Controller
{
// id is the id of the article
public ActionResult Read(int id)
{
// Get the article from database by id
var model = db.Articles.Find(id);
// Return the view
return View(model);
}
}
It is explained pretty well here: Display a view from another controller in ASP.NET MVC
To quote #Womp:
By default, ASP.NET MVC checks first in \Views\[Controller_Dir]\,
but after that, if it doesn't find the view, it checks in \Views\Shared.
ASP MVC's idea is "convention over configuration" which means moving the view to the shared folder is the way to go in such cases.
So, basically what I'd like is to do something like:
#if(Notification!=null){
//perform javascript notification with #Notification.Text
}
And I'd like to be able to do this on any view, so I will always have the option of specifying a notification object in my controller action, that, if defined, can be handled in the view.
My dream scenario is to allow this simply by creating the Notification object somehow, and then just returning the view. Meaning, I wouldn't need to explicitly pass the Notification object to the model. Like so:
public ActionResult MyAction(){
Notification n = new Notification("Text for javascript");
return View();
}
I'm thinking, that there might be a way to do this with some ViewPage-inheritance? But I'm really unsure of how to go about this?
In an ideal world, I would also love to be able to "override" what to do. For example, if I in my 'top'-layout choose to perform a certain kind of jquery-notification if the notification object exists, but maybe in some other nested view would like to handle it differently, I'd like the option to override the top-layouts handling of the object.
I know this last thing might be a little utopian (I'm just starting out with MVC and Razor), but it would be cool :)
You could write a custom global action filter which will inject this information on all views. For example:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.Controller.ViewBag.Notification = new Notification("Text for javascript");
}
}
and then register this filter in the RegisterGlobalFilters method of your Global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new MyActionFilterAttribute());
}
And then in your views:
#if(ViewBag.Notification != null)
{
//perform javascript notification with #ViewBag.Notification.Text
}
Use ViewBag for simple stuff like popup message.
public ActionResult Index()
{
ViewBag.PopupMessage = "Hello!";
return View();
}
and then in view (or layout page)
#if (ViewBag.PopupMessage != null)
{
<div class="popup">#ViewBag.PopupMessage</div>
}
For more complicated stuff you will need to either create static class and save/read from HttpContext.Current.Items or override Controller and WebViewPage and save/read from ViewBag/ViewData.
Update:
public abstract class BaseController : Controller
{
public const string NotificationKey = "_notification";
protected string Notification
{
get
{
return ViewData[NotificationKey] as string;
}
set
{
ViewData[NotificationKey] = value;
}
}
}
public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
protected string Notification
{
get
{
return ViewData[BaseController.NotificationKey] as string;
}
}
}
Views/Web.config
<pages pageBaseType="OverrideTest.Framework.BaseViewPage">
Usage:
public class HomeController : BaseController
{
public ActionResult Index()
{
Notification = "Hello from index!";
return View();
}
}
<div>Notification: #(Notification ?? "(null)")</div>
Or get test project here
Update 2:
Check out this blog post for another way to do something similar.