Can one Controller share data to other Controllers - asp.net-mvc

In one of my Controllers Action Method I get the current week number and it's passed to the View. I realize that I could need the week number in other Controllers and Views, but I don't want to copy/paste the same code at different places in my project.
I'm looking for a simple and smart solution where I can reuse the code from just one place like a static class or a global object or something similar. If it was within the same Controller I could have done a method to call, but since I need it from other Controllers, I need another solution and I'm not sure how to achieve this? Any suggestions?

You can create a class that inherits from Controller.
I create a BaseController class like so:
public class BaseController : Controller
{
protected someDBContext db = new someDBContext();
protected User currentUser;
public static ILog ErrorLogger = LogManager.GetLogger("ErrorLogger");
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
if (Session["UserID"] != null)
{
currentUser = UserToStaff.GetCurrentUser(
(int)Session["userId"],
db
);
}
ViewBag.currentUser = currentUser;
addNoticesToViewBag();
}..
I add functions and properties for common tasks like
creating the dbContext, creating the currentUser object, and setting up logging in here.
Don't let it grow arms and legs though.. the Initialize is going to fire with every request.

Related

ASP.NET MVC 4, how to access/modify the view model object (and change view and action method) before it is used as action method parameter?

Is there any useful hook in ASP.NET MVC (MVC4) which can let you access the Action method parameter (View model) before the action method becomes invoked, and then also (e.g. depending on the value of something you checked in the action method parameter) let you prevent the action method from being invoked, i.e. instead either forward the view model object (action method parameter) to another action method or directly to some view (i.e. without any further processing in an action method) ?
If you do not understand the question, please see the code example below which should illustrate the kind of code I am looking for...
(though I do not know if there actually exists such kind of interface and a possibility to hook an implementation into the MVC framework)
If this is indeed possible, I would like to see an answer with code example about how to do it (and not just a response with someone claiming that e.g. "try using method 'ActionFilterAttribute.OnActionExecuting' or 'IModelBinder.BindModel' " because I have already tried those and could not make it work).
Also, please respect that I do not want this thread to become a discussion about WHY to do it, but want to see HOW to do it.
(i.e. I am not interested in getting into discussions with responses such as "What are you actually trying to achieve?" or "There are probably better things of doing what you want to do...")
The question can be split into three subquestions/code examples as my own code samples below try to illustrate:
(but would like them "refactored" into REAL code with usage of real existing types)
(obviously, every type below which includes the substring "Some" is something I have made up, and I am looking for the corresponding real thing ...)
(1) Example of how to get access to (and potentially modify) view model objects (action method parameters) in a generic place before the actual action method is invoked with the view model object parameter.
The kind of code example I am looking for would probably be similar to below but do not know what kind of interface to use and how to register it to be able to do something like below:
public class SomeClass: ISomeInterface { // How to register this kind of hook in Application_Start ?
public void SomeMethodSomewhere(SomeActionMethodContext actionMethodContext, object actionMethodParameterViewModel) {
string nameOfTheControllerAboutToBeInvoked = actionMethodContext.ControllerName;
string nameOfTheActionMethodAboutToBeInvoked = actionMethodContext.MethodName;
// the above strings are not used below but just used for illustrating that the "context object" contains information about the action method to become invoked by the MVC framework
if(typeof(IMyBaseInterfaceForAllMyViewModels).IsAssignableFrom(actionMethodParameterViewModel.GetType())) {
IMyBaseInterfaceForAllMyViewModels viewModel = (IMyBaseInterfaceForAllMyViewModels) actionMethodParameterViewModel;
// check something in the view model:
if(viewModel.MyFirstGeneralPropertyInAllViewModels == "foo") {
// modify something in the view model before it will be passed to the target action method
viewModel.MySecondGeneralPropertyInAllViewModels = "bar";
}
}
}
}
(2) Example of how to prevent the targeted action method from being executed and instead invoke another action method.
The example might be an extension of the above example, with something like below:
public void SomeMethodSomewhere(SomeActionMethodContext actionMethodContext, object actionMethodParameterViewModel) {
... same as above ...
if(viewModel.MyFirstGeneralPropertyInAllViewModels == "foo") {
actionMethodContext.ControllerName = "SomeOtherController";
actionMethodContext.MethodName = "SomeOtherActionMethod";
// The above is just one example of how I imagine this kind of thing could be implemented with changing properties, and below is another example of doing it with a method invocation:
SomeHelper.PreventCurrentlyTargetedActionMethodFromBecomingExecutedAndInsteadExecuteActionMethod("SomeOtherController", "SomeOtherActionMethod", actionMethodParameterViewModel);
// Note that I do _NOT_ want to trigger a new http request with something like the method "Controller.RedirectToAction"
}
(3) Example of how to prevent the normal action method from being executed and instead forward the view model object directly to a view without any further processing.
The example would be an extension of the first above example, with something like below:
public void SomeMethodSomewhere(SomeActionMethodContext actionMethodContext, object actionMethodParameterViewModel) {
... same as the first example above ...
if(viewModel.MyFirstGeneralPropertyInAllViewModels == "foo") {
// the below used razor view must of course be implemented with a proper type for the model (e.g. interface 'IMyBaseInterfaceForAllMyViewModels' as used in first example above)
SomeHelper.PreventCurrentlyTargetedActionMethodFromBecomingExecutedAndInsteadForwardViewModelToView("SomeViewName.cshtml", actionMethodParameterViewModel);
}
You could use an action filter and override the OnActionExecuting event:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
...
}
}
Now let's see what useful information you could extract from this filterContext argument that is passed to this method. The property you should be looking for is called ActionParameters and represents an IDictionary<string, object>. As its name suggests this property contains all the parameters that are passed to the controller action by name and value.
So let's suppose that you have the following controller action:
[MyActionFilter]
public ActionResult Index(MyViewModel model)
{
...
}
Here's how you could retrieve the value of the view model after model binding:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = filterContext.ActionParameters["model"] as MyViewModel;
// do something with the model
// You could change some of its properties here
}
}
Now let's see the second part of your question. How to shortcircuit the controller action and redirect to another action?
This could be done by assigning a value to the Result property:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
... some processing here and you decide to redirect:
var routeValues = new RouteValueDictionary(new
{
controller = "somecontroller",
action = "someaction"
});
filterContext.Result = new RedirectToRouteResult(routeValues);
}
}
or for example you decide to shortcircuit the execution of the controller action and directly render a view:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var viewResult = new ViewResult
{
ViewName = "~/Views/FooBar/Baz.cshtml",
};
MyViewModel someModel = ... get the model you want to pass to the view
viewResult.ViewData.Model = model;
filterContext.Result = viewResult;
}
}
or you might decide to render a JSON result:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
MyViewModel someModel = ... get the model you want to pass to the view
filterContext.Result = new JsonResult
{
Data = model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
So as you can see the possibilities are unlimited of what you can do.
I have experimented with the code in the answer provided by the user Darin Dimitrov, and the first and third parts of the answer are correct.
(Though, for others who might find this thread and be interested, I can clarify that in the first answer the "model" does not seem to
be a hardcoded keyword always used for the model but seems to have to correspond to the chosen name of the action method parameter.
In other words, if you instead have the method signature
public ActionResult Index(MyViewModel myViewModel)
then in your action filter you have to use
var model = filterContext.ActionParameters["myViewModel"] as MyViewModel;
)
Regarding the second answer, the usage of 'RedirectToRouteResult' will trigger a new http request (which was not desired as I mentioned in the second code example of mine).
I found another way of "changing" action method by actually invoking it explicitly:
var controller = new SomeController();
ActionResult result = controller.SomeAction(model);
filterContext.Result = result;
The above code actually seems to prevent the originally targeted action method from becoming invoked, i.e. when I put a breakpoint in the method annotated with '[MyActionFilter]' the execution never got into that method.
Typically, it is probably not desired to hardcode a controller like above, but instead reflection might be used, for example as below with the thirdpart library "fasterflect":
string nameOfController = ...
string nameOfActionMethod = ...
// both above variables might for example be derived by using some naming convention and parsing the refering url, depending on what you want to do ...
var theController = this.GetType().Assembly.CreateInstance(nameOfController);
ActionResult result = (ActionResult)theController.CallMethod(nameOfActionMethod, model);
filterContext.Result = result;
(for those who want to extract the names of the current target controller and action method, when implementing logic to determine the controller you want to invoke, you can use this code in the filter:
var routeValueDictionary = filterContext.RouteData.Values;
string nameOfTargetedController = routeValueDictionary["controller"].ToString();
string nameOfTargetedActionMethod = routeValueDictionary["action"].ToString();
)
I think it feels a bit awkward to instantiate and invoke controllers like above, and would prefer to change the target controller and action method in another way if possible ?
So, the remaining question is if there is still (in MVC 4 final version) no way of redirecting/forwarding execution "internally" (without a new http request being fired as with 'RedirectToAction') at the server ?
Basically, I think I am here just looking for something like "Server.Transfer" which was used with ASP.NET Web Forms (and also the old classic ASP I believe could use the same thing).
I have seen older question/answers on this issue with people implementing this behaviour themselves with some "TransferResult" class of their own, but it seems to tend to become broken i different MVC versions.
(for example, see here for MVC 4 beta: How to redirect MVC action without returning 301? (using MVC 4 beta) ).
Is there really still not a simple standard solution (implemented in MVC 4 final) about how to do an "internal redirect" without a new http request (as RedirectToAction does) ?

mvc best practice for common code

I have an MVC application. The following code is being used in multiple places within a controller and in multiple controllers. I would like to have this code in one place and call it from each location. What's the best way to do that in MVC?
The code below gets a row from the database and creates ViewData that can be read from the view. With webforms, I would create a public sub within a class and pass the year and month values. Is there a way this code could become part of the model?
var monthlyexpenseincome = (from vu_monthlyresult in dbBudget.vu_MonthlyResults
where vu_monthlyresult.Month == defaultmonth && vu_monthlyresult.Year == defaultyear
select vu_monthlyresult).Single();
var yearlyexpenseincome = (from vu_yearlyresult in dbBudget.vu_YearlyResults
where vu_yearlyresult.Year == defaultyear
select vu_yearlyresult).Single();
ViewData["MonthlyExpenses"] = monthlyexpenseincome.Expenses;
ViewData["MonthlyIncome"] = monthlyexpenseincome.Income;
ViewData["MonthlyProfit"] = monthlyexpenseincome.Income - monthlyexpenseincome.Expenses;
Generally, If you have common code across multiple controllers, you can create another class which inherits from Controller and keep your methods there and let your indidual Controlellers inherit this new class
public class BaseController : Controller
{
protected string GetThatInfo()
{
//do your magic logic and return some thing useful
return "This is demo return.Will be replaced";
}
}
And now you can inherit from this for your other controllers
public class UserController: BaseController
{
public ActionResult Index()
{
return VieW();
}
}
But In your case, The data you are taking is something specific to your Domain data. so i would suggest you to move it to a different class ( like a new Service / Business Layer)
public static class ProfitAnalysis
{
public static decimal GetTotalExpense()
{
//do your code and return total
}
}
And you can call it from wherever you wanted like
decimal totalExp=ProfitAnalysis.GetTotalExpense();
And You will soon realize, Having so much ViewData usage is making your code difficult to read and Maintain. Do not wait for that day. Switch to strongly typed classes to pass data.
You should place your queries in a "business layer", which is just a class that you call to do the business logic. Then you can reuse it in any place you like, just instantiate the business class and use it. You could also make the methods static if they don't require state, then you wouldn't have to even instantiate it.
For example:
var expenseService = new expenseService();
ViewData["MonthlyExpenses"] = expenseService.GetMonthlyExpenses();

Where is the BEST PRACTICES place to put a list of Countries in a MVC Application?

In a heavy enviroment application, we have Users, Locations, bla bla bla... and we use in many situations a call to a service where we retrieve the list of countries.
Where is the 'best practice' or 'proper way' to implement this. This method is called in several places and many objects has a List<CountryVO> property.
Specially considering using Razor views an often having to add this property to ModelViews
The solution is using DAL / BLL / SERVICE / UI[s] architecture.
Real Example:
public class User {
...
...
public List<DeliveryZoneVO> DeliveryZones {get;set;}
public User() {
...
DeliveryZones = service.GetDeliveryZones().ToList();
}
}
The class DeliveryZoneVO comes from a webservice, so one property is
int IdCountry
The class User have a list of DeliveryZoneVO as presented on the class, the 'problem' here, is since it retrieves the data from a web service, I only have the ID of the country.
When I prepare the data in the controller to send to the View:
UserModelView userMV = new UserModelView();
userMV.user = service.GetUserById(1);
ViewData.Model = userMV;
BUT, inside userMV.user, I have DeliveryZones with a list of DeliveryZoneVO objects with IdCountries.
In the view, when I do (for example) :
#DisplayFor(m => m.user.DeliveryZones)
I want to show the Country Name, only have the ID. So i need a reference somewhere.. the question lies in where should that data needs to be placed that is considered BEST PRACTICES.
Is having in all modelview (in the case of the example, the UserModelView() the property Countries with a List ?
A good thing because this kind of issues is to have a BaseController class that derived from controller, and all the other controllers you have derived from it.
in the BaseController put a static List<CountryVO> property with getter only, this way it will be initialized once and will be accessible to all of your's controllers and views(If you pass it with the ViewModel or ViewBag).
Example:
public class BaseController : Controller
{
private static List<CountryVO> _allCountries;
public static List<CountryVO> AllCountries
{
get{ return _allCountries ?? _GetCountriesFromSomeWhere();}
}
}
public class HomeController : BaseController
{
public ActionResult Index()
{
ViewBag.AllCountries = this.AllCountries;
return View();
}
}
I would create a partial view that is responsible for just rendering the country list. Then any changes to how the list is rendered is can be made in just one place. I would create a model class that encapsulates calling the service to get the countries. Assuming that the country list is fairly static you could handle caching of the information in the model class for less calls to the service and better performance. Below is an example of a method in the model that gets the country list from the server cache if it is available.
const string cacheId = "deliveryZones";
public List<DeliveryZoneVO> GetDeliveryZones()
{
List<DeliveryZoneVO> deliveryZones = (List<DeliveryZoneVO>)HttpRuntime.Cache.Get(cacheId);
if (deliveryZones == null)
{
deliveryZones = service.GetDeliveryZones().ToList();
System.Web.HttpContext.Current.Cache.Insert(cacheId, deliveryZones);
}
return deliveryZones;
}

Persist data between different controllers from a base page

I am not sure I am asking the right question here.
I have a shared page (master page) that calls a couple of partial pages for side menu, header, footer etc.. and all my controllers inherit a BaseController.
Now, depending on the user login status, I need to show different data in all those partial pages and I thought where is the best place to check whether a user is logged in or not - BaseController.
And therein lies my problem. I need to contact one of my web services to see if a user is logged in and get some relevant data if he is. I only need to do this once, and since all controllers inherit from BaseController, each of those partial page calls results in the web service call.
Obviously, I cannot just stick a private bool variable isUserAuthenticated and check for flag, as, each controller will have a new instance of the base controller.
In traditional asp.net projects, I would put this stuff in HttpContext.Current.Items[] and use re-use it but I cannot (somehow) access that in MVC.
I cannot just not inherit from basepage on partial pages as they can also be called independently and I need to know the user login status then too.
What is the best way to call a function just once, or, rather, store a bool value for the duration of one call only? - accessible between controlers..
How do people do this?
thanks, sorry, I'm a newbie to mvc!
You can still use HttpContext.Items, but you'll need to access it via a HttpContextBase instance.
For backwards compatibility you can wrap an HttpContext in an HttpContextWrapper, like so
var context = new HttpContextWrapper(HttpContext.Current);
#iamserious's answer above suggests using a static property - which I strongly disagree with. Setting a static variable is application wide and would mean each and every user would be using the same variable - so all would have the same login data. You want to store it either per user in Session or per Request via HttpContext.Items.
I'd suggest doing something using like this approach, then no matter where you call ContextStash.GetInstance, you'll receive the same instance for the lifetime of the same request. You could also follow the same pattern and use HttpContext.Session instead of HttpContext.Items:
// could use this.HttpContext inside a controller,
// or this.Context inside a view,
// or simply HttpContext.Current
var stash = ContextStash.GetInstance(this.HttpContext);
if(!stash.IsSomething)
{
// do something to populate stash.IsSomething
}
// class
public class ContextStash
{
const string cacheKey = "ContextStash";
public ContextStash(HttpContextBase context)
{
// do something with context
}
// your shared properties
public bool IsSomething { get; set; }
public string Foo { get; set; }
public int Bar { get; set; }
// instance methods
public static ContextStash GetInstance()
{
return GetInstance(new HttpContextWrapper(HttpContext.Current));
}
public static ContextStash GetInstance(HttpContext context)
{
return GetInstance(new HttpContextWrapper( context ));
}
public static ContextStash GetInstance(HttpContextBase context)
{
ContextStash instance = context.Items[cacheKey] as ContextStash;
if(null == instance)
{
context.Items[cacheKey] = instance = new ContextStash(context);
}
return instance;
}
}
well, if you just want to one variable across several instances of BaseController, use the static keyword, like so:
public class BaseController : Controller
{
private static bool isUserAuthenticated;
}
Now, no matter how many instances of BaseController you have, they all will share a single isUserAuthenticated variable, you change value in one, you change it in all.
This is the very basic of most object oriented programming and you should really take some time out to go through the concepts of OOP, if you don't mind me saying.

ASP.NET MVC 1 - Calling the Session object in ViewModel

How can I call the Session in ViewModel?, Reference to "Session [...]" or " HttpContext.Session [..]" not exist in this context
try
HttpContext.Current.Session[..]
The general idea is that you "shouldn't."
Your controller should provide all of the information that the view needs.
However, it may be worthwhile to pass the session (or pieces of it) along with the ViewModel.
The way I handle this is, I have a base class for all of my view models that have access to the controller. They can then directly query the controller for specific objects from the session without ever exposing the session directly to the view.
BaseView.cs
public abstract class BaseView<TModel> : SparkView<TModel> where TModel : ControllerResponse
{
// Stuff common to all views.
}
ControllerResponse.cs (base model for all views)
public class ControllerResponse
{
private BaseController controller = null;
private ControllerResponse() { }
public ControllerResponse(BaseController controller)
{
this.controller = controller;
}
// Here, you would place all of the methods that the BaseView should have access to.
}

Resources