MVC global filter to load/maintain menus from database on page load - asp.net-mvc

I am working on MVC5 applications where I have a base controller which is inherited by every controller in my application.
I am using OnActionExecuting function of base controller to load and maintain menus from database.
As this function will be called every-time any controller inherit base controller so some times it has been called more than once.
Can I use/create any other function from base controller which will be called once when view is about to render.
Is there any better way to maintain menus for the same user as in such case no need to hit database on every page, TIA.

Good idea will be to use caching. For instance Cache Class where you can store objects and retrieve them by key.
public abstract class ControllerBase
{
private readonly Cache _cache;
public ControllerBase(Cache cache)
{
// null check
_cache = cache;
}
protected virtual void OnActionExecuting(ActionExecutingContext filterContext)
{
string username = filterContext.HttpContext.User.Identity.Name;
// authenticate duser has always username
if(!String.IsNullOrEmpty(username))
{
var cacheItem = _cache.Get("menu_" + username);
if(cacheItem is {Type})
{
}
else
{
// Load from db
// Add to cache with sliding expiration
}
}
else
{
var cacheItem = _cache.Get("menu_anonymous_user");
if(cacheItem is {Type})
{
}
else
{
// Load from db
// Add to cache with sliding expiration
}
}
}
}
It is an example. It can be done much better, but for an idea it is enough, I guess.

Related

How to share same data one action method to other action method in ASP.NET MVC

When i call my admin controller- Index Action method will get all the user details
when i want select particular user again i dont want to hit the DB.
both action method same controller and i'm using model popup for display details.
My Question
I dont want to use entity framework.
- when admin form load i will get all the user details this is Index Action Method
-based on user id i need to display particular user so again i dont want hit to the DB already i'm having all the user details. that details how to get another action method?
i can remember asp.net i used session to share the data globally. like that asp.net mvc is possible? please help me.
Thanks
It looks you're looking for a cache mechanism. For simple scenarios, I use a simple static variable, but I keep it in a separated class. Let's suppose you have a User class like this:
public class User
{
public string Id { get; set; }
public string Name { get; set; }
}
You could create a class like this:
public static class UserCacheService
{
private static IEnumerable<User> _users;
private static readonly object lockObj = new object();
public static IEnumerable<User> GetUsers()
{
lock (lockObj)
{
if (_users == null)
{
using (var db = new MyNiceDbContext())
{
_users = db.Users.ToList();
}
}
return _users;
}
}
public static void InvalidateCache()
{
lock (lockObj)
{
_users = null;
}
}
}
Then you can get your shared users in any action, of any controller like this:
public class AdminController : Controller
{
public ActionResult Index()
{
// the first time, it'll need to get users from DB (e.g with Entity Framework)
var users = UserCacheService.GetUsers();
return View();
}
}
The first time, the _users in your UserCacheService will be null, and as expected, it'll need to load users from database. However, the next time it won't, no matter if you are using another controller:
public class AnotherController : Controller
{
public ActionResult Index(string userId)
{
// now, it won't load from DB anymore, because _users is already populated...
var users = UserCacheService.GetUsers();
var currentUser = users.Where(u => u.Id == userId).FirstOrDefault();
if (currentUser != null)
{
// do something with the user...
}
return View();
}
}
There are times when unfortunately your _users will become null again, for example when you restart your ApplicationPool in IIS, but UserCacheService is already prepared for fetching database once if that's the case.
Be careful about three things:
Whenever you keep data in memory (like _users), you are consuming
your server's memory, which might be limited. Don't start trying to
keep everything in memory, only data you know you'll need everytime.
Whenever you update something in your users, like a name, an address or something else, since the _users will not get from database everytime, you need to call the UserCacheService.InvalidateCache() method, in order to force the next call to load again from database, thus making sure you have _users up to date.
This only works for simplistic scenarios. If you have your application distributed in two or more servers, this won't work, as each server has it's own memory and they can't share it out of the box. That's when you would look forward for something like Redis. Though, I don't think it's your case here.

asp.net mvc web api 2 custom logger + controller lifecycle

story:I have following component in my asp.net mvc 5 web api project(using ef code first)
myBaseController : base api controller class
myLogger : my costume logger class
class myBaseController { MyLogger Logger; }
class MyLogger
{
public addLog(Log log)
{
db.logs.add(log);
db.saveChanges();
}
}
Each api controller use base.logger.addLog(new log("somethings")) and the log committed to database immediately.
Problem: I want MyLogger class be able to collect all log in memory and some where add theme to database at once.
I put it in
protected override void Dispose(bool disposing)
{
logger.AddRange(_logs);
}
but it doesn't work,it seems objects are not available in this method or grabbed from memory
How it should be handled?
You don't need to collect records in memory separately. Because EF will do that for you. For instance db.logs.add means, please add it to memory. But when you call SaveChanges it will commit all of the in-memory collected data to the database at once. So you will need to implement the unit of work pattern (UoW). By using this pattern, you will have only one context per request and then at the end of the request you will call the SaveChanges method once. You shouldn't have multiple SaveChanges everywhere in your codes, this is your problem right now. Your addLog method calls SaveChanges, your other methods call SaveChanges and so on.
More info from Microsoft: Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application
You need to store your entries in memory during request cycle and at end of request you can save those entries into database
protected void Application_EndRequest()
{
//get entries from memory/session/cache whatever your source to store entries
//do stuff to save entries
}
I normally create ActionFilterAttributes, and place it on action methods where I want to log any activity.
TraceMvcAttribute
public class TraceMvcAttribute : ActionFilterAttribute
{
// IoC container will inject this dependencies.
public IWebUserSession WebUserSession { get; set; }
public IDateTime DateTime { get; set; }
public ITraceListener TraceListener { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var traceLog = new TraceLog
{
Controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
Action = filterContext.ActionDescriptor.ActionName,
Message = JsonConvert.SerializeObject(filterContext.ActionParameters),
PerformedOn = this.DateTime.Now,
PerformedBy = WebUserSession?.UserName
};
TraceListener.AddTraceLogAsync(traceLog);
base.OnActionExecuting(filterContext);
}
}
TraceApiAttribute
Web API filter is a little bit different from MVC filter. I do not have it in my sample code at Git; in case you might want to take a look at it.
public class TraceApiAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
// http://autofac.readthedocs.org/en/latest/integration/webapi.html#standard-web-api-filters-are-singletons
// All filter attributes in Web API are effectively singleton instances that exist
// for the entire lifetime of the application. We must use service location,
// since we need per-request services in your filters.
var requestScope = actionContext.Request.GetDependencyScope();
var datetime = requestScope.GetService(typeof(IDateTime)) as IDateTime;
var webUserSession = requestScope.GetService(typeof(IWebUserSession)) as IWebUserSession;
var traceListener = requestScope.GetService(typeof(ITraceListener)) as ITraceListener;
var traceLog = new TraceLog
{
Controller = actionContext.ControllerContext.ControllerDescriptor.ControllerName,
Action = actionContext.ActionDescriptor.ActionName,
Message = JsonConvert.SerializeObject(actionContext.ActionArguments.Where(x => x.Key != "request").ToList()),
PerformDateTime = datetime.Now,
PerformBy = webUserSession?.UserName
};
traceListener.AddTraceLogAsync(traceLog);
base.OnActionExecuting(actionContext);
}
}
I then view the log history like this -

NHibernate session is closed when refreshing page

This is another strange problem I've encountered this days!!! I've created and MVC 4 app using nhibernate. and added a filter attribute named [LoggingNHibernateSessionAttribute] on my HomeController which manages session for each action. I've followed 'ASP.NET MVC4 and the Web API published by Apress'.
public class LoggingNHibernateSessionAttribute : ActionFilterAttribute
{
private readonly IActionLogHelper _actionLogHelper;
private readonly IActionExceptionHandler _actionExceptionHandler;
private readonly IActionTransactionHelper _actionTransactionHelper;
public LoggingNHibernateSessionAttribute()
: this(WebContainerManager.Get<IActionLogHelper>(),
WebContainerManager.Get<IActionExceptionHandler>(),
WebContainerManager.Get<IActionTransactionHelper>())
{
}
public LoggingNHibernateSessionAttribute(
IActionLogHelper actionLogHelper,
IActionExceptionHandler actionExceptionHandler,
IActionTransactionHelper actionTransactionHelper)
{
_actionLogHelper = actionLogHelper;
_actionExceptionHandler = actionExceptionHandler;
_actionTransactionHelper = actionTransactionHelper;
}
public override void OnActionExecuting(ActionExecutingContext actionExectingContext)
{
_actionLogHelper.LogEntry(actionExectingContext.ActionDescriptor);
_actionTransactionHelper.BeginTransaction();
}
public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)
{
_actionTransactionHelper.EndTransaction(actionExecutedContext);
_actionTransactionHelper.CloseSession();
_actionExceptionHandler.HandleException(actionExecutedContext);
_actionLogHelper.LogExit(actionExecutedContext.ActionDescriptor);
}
}
ActionTransactionHelper
public class ActionTransactionHelper : IActionTransactionHelper
{
private readonly ISessionFactory _sessionFactory;
private readonly ICurrentSessionContextAdapter _currentSessionContextAdapter;
public ActionTransactionHelper(
ISessionFactory sessionFactory,
ICurrentSessionContextAdapter currentSessionContextAdapter)
{
_sessionFactory = sessionFactory;
_currentSessionContextAdapter = currentSessionContextAdapter;
}
public void BeginTransaction()
{
var session = _sessionFactory.GetCurrentSession();
if (session != null)
{
session.BeginTransaction();
}
}
public bool TransactionHandled { get; private set; }
public void EndTransaction(ActionExecutedContext filterContext)
{
var session = _sessionFactory.GetCurrentSession();
if (session == null) return;
if (!session.Transaction.IsActive) return;
if (filterContext.Exception == null)
{
session.Flush();
session.Transaction.Commit();
}
else
{
session.Transaction.Rollback();
}
TransactionHandled = true;
}
public bool SessionClosed { get; private set; }
public void CloseSession()
{
if (_currentSessionContextAdapter.HasBind(_sessionFactory))
{
var session = _sessionFactory.GetCurrentSession();
session.Close();
session.Dispose();
_currentSessionContextAdapter.Unbind(_sessionFactory);
SessionClosed = true;
}
}
}
when run the app, I can save an entity in the dataBase. but when I hit refresh button and exception thrown indication session is closed.
I don't know why this happens. (I searched and find this NHibernate throwing Session is closed but couldn't solve my problem).
in my NinjectConfigurator I added inRequestScope() to all of injections but no answer. I checked when I refresh the page session will be opened. but I donnow why it say session is closed?!
UPDATE:
when I first run the app. I can create a new member. but when I hit the refresh button, the session will be closed unexpectedly!!
first run:
everything works well
after hitting refresh button:
a new session bind to the current context.
the new session will be injected the repository (session is open)
the ActionTransactionHelper calls beginTransaction()
4- customMembership createUser (....) called
5- but when the _userRepositoy.save(user)called in the repository session is closed!!!!
note:but when still endTransaction and closeSession isn't called. but how session is closed?
if I comment closeSession() in onActionExecute(). session alway is open and everything woks well if refresh the page.
I checked a lot and tried different way I knew. it only happens when for the second time I want to do CRUD operations with my customMembership.
for other entities it works like a charm!
I have upoaded my sample code. for testing just create and empty database and change connection string. then go to localHost:*****/api/categories (user and pass doesn't required)
Download sample project:
Size: 47 MB
https://www.dropbox.com/s/o63wjng5f799fii/Hashem-MVC4ServicesBook.rar
size: 54 MB
Zip Format: https://www.dropbox.com/s/smrsbz4cbtznx1y/Hashem-MVC4ServicesBook2.zip
A very important thing here, could be the nature of the NHibernate. The NHibernate and its Session are in the ASP.NET MVC living longer, then could be expected. I mean not only inside of the
ActionExecuting (Controller Action starts)
ActionExecuted (the View or Redirect is called)
Session in fact must live also through the phase of rendering. Because, we could load some proxy in the "Action()" but its collection, could be lazily loaded only during the View rendering. So even in these phases Session must be opened (the same Session from the request begining)
ResultExecuting (the proxy could start to be loaded only here)
ResultExecuted (almost all is done, let's close the session)
Other words... keep the session opened throught the complete Request. From authorization untill the content is rendered.
NOTE: Anohter hint, just to be sure that all is ok, I am using this scenario (maybe you do as well):
Client FORM is about to send the data to server. The method is POST, the Action is Update()
Sent FORM is coming to server, Action Update() is triggerred - all the transactions stuff is in place (as described above)
Once NHibernate persists the data into DB, the Update() action ends, and is redirected to action
Detail() if all is ok or
Edit() if something goes wrong
The users Browser was redirected to action Detail or Edit. So if user does REFRESH, the Detail or Edit is refreshed. The Update() is not called at all (it is a POST method)
In fact, the step 1. was one of the Actions Detail or Edit. In this case, we would face this issue already...
You have this error since Asp.Net MVC does not create a new instance of LoggingNHibernateSessionAttribute every request. It creates a new instance when you request an action first time and then uses this instance in the future.
The behaviour is the following:
First invocation of Post -> new instance of 'LoggingNHibernateSession' is created
First invocation of Put -> another one instance of 'LoggingNHibernateSession' is created
Second invocation of Put -> instance of 'LoggingNHibernateSession' from previous step is used
First invocation of Delete -> another one instance of 'LoggingNHibernateSession' is created
[LoggingNHibernateSession]
public JsonResult Post(Dto data)
{
/* ... */
}
[LoggingNHibernateSession]
public JsonResult Put(int id, Dto data)
{
/* ... */
}
[LoggingNHibernateSession]
public JsonResult Delete(int id)
{
/* ... */
}
It can be solved using Func<IActionLogHelper> instead of IActionLogHelper in the constructor. An instance of IActionLogHelper can be initialised within OnActionExecuting method.
public class LoggingNHibernateSessionAttribute : ActionFilterAttribute
{
/* your code */
private readonly Func<IActionTransactionHelper> _getActionTransactionHelper;
private IActionTransactionHelper _actionTransactionHelper;
public LoggingNHibernateSessionAttribute()
: this(WebContainerManager.Get<IActionLogHelper>(),
WebContainerManager.Get<IActionExceptionHandler>(),
() => WebContainerManager.Get<IActionTransactionHelper>())
{
}
public LoggingNHibernateSessionAttribute(
IActionLogHelper actionLogHelper,
IActionExceptionHandler actionExceptionHandler,
Func<IActionTransactionHelper> getActionTransactionHelper)
{
_actionLogHelper = actionLogHelper;
_actionExceptionHandler = actionExceptionHandler;
_getActionTransactionHelper = getActionTransactionHelper;
_actionTransactionHelper = null;
}
public override void OnActionExecuting(ActionExecutingContext actionExectingContext)
{
_actionTransactionHelper = _getActionTransactionHelper();
_actionLogHelper.LogEntry(actionExectingContext.ActionDescriptor);
_actionTransactionHelper.BeginTransaction();
}
/* your code */
}

MVC3 - How to correctly inject dependencies with MVC3 and Ninject?

I am attempting to redesign an existing application using dependency injection with Ninject in MVC3. Here is a portion of the legacy behavior I'm having difficulty with (and yes I know its bad, that's why I'm trying to refactor it):
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
MyUserSession userSession = filterContext.HttpContext.Session[SESSIONKEY_USER] as MyUserSession;
// if session empty, rebuild user information
if (userSession == null)
{
string userName = HttpContext.User.Identity.Name;
userSession = new MyUserSession();
using (ADSearcher ad = new ADSearcher(ldapPath, excludeOUString.Split(',')))
{
// get basic user information from Active Directory
ADUserInfo aduser = MyActiveDirectorySearcher.GetUserRecord(userName);
// ... set several properties queries from AD...
userSession.propertyXYZ = aduser.propXYZ
}
// if user can proxy as another indivudual, set property
using (EDMContainer db = new EDMContainer())
{
if (db.Proxies.Any(p => p.ProxyLogin == userSession.userLogin))
userSession.CanProxy == true;
}
// save new user object to session
filterContext.HttpContext.Session[SESSIONKEY_USER] = userSession;
if(userSession.canProxy)
filterContext.Result = RedirectToAction("Proxy", "Home");
return;
}
}
So currently, the controller users several objects directly: Session, ActiveDirectorySearch, EF Database. I understand it would be better to create a class that exposes a single method "GetUser" masking all the complexity but I'm struggling with how to inject the dependencies.
If I create a class SomeUserProvider, it will also need access to the Session to check for existing user information, and then ActiveDirectorySearcher and Database to rebuild the user properties if session was empty.
My confusion is over the fact that the controller itself will need access to ActiveDirectorySearcher in other action methods and then other classes will also use the same database. Do I inject an IActiveDirSearchrer into the controller's constructor and then pass it down into the ISomeUserProvider? What about IMyDatabase? Is it also injected in controller constructor and passed down?
And last but not lease, ISessionWrapper? I know session is controversial, but I need to track who the current user is and who they are proxied as during each request (GETs and POSTs). So, does that get injected as well?
If the answer is yes to each of those, is it bad to have 3+ injected contstuctor parameters?
I realize my question may be vague, so please ask for clarification where needed. I am open to any and all suggestions and recommendations. My goal is to learn how to do it correctly.
Thanks.
I'm not certain if this is exactly what you're looking for, but this should get you started down the path of refactoring your app for DI
public class YourController : Controller
{
private readonly ISessionWrapper _sessionWrapper;
private readonly IActiveDirSearcher _adSearcher;
private readonly IMyDatabase _database;
public YourController(ISessionWrapper sessionWrapper,
IActiveDirSearcher adSearcher, IMyDatabase database)
{
this._sessionWrapper = sessionWrapper;
this._adSearcher = adSearcher;
this._database = database;
}
// now all actions in this controller have a _sessionWrapper,
// _adSearcher and _database
}
Then you have to bind your injections the Ninject way. Subclass your application from NinjectHttpApplication and override OnApplicationStarted and CreateKernel
public class MvcApplication : NinjectHttpApplication
{
// ...
protected override void OnApplicationStarted()
{
base.OnApplicationStarted();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
protected override IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<ISessionWrapper>().To<YourSessionWrapperImplementation>();
kernel.Bind<IActiveDirSearcher>().To<YourADImplementation>();
kernel.Bind<IMyDataBase>().To<YourEDMContainerIThink>();
return kernel;
}
}
The implementations of these appear to be described in your question. However, you mentioned other actions (and other classes) depend on these implementations. Good news--the bindings in CreateKernel will take care of any missing dependencies elsewhere in your app. e.g.
public class MyActiveDirImplementation : IActiveDirSearcher
{
private readonly IMyDatabase _database;
// injected automagically WOOHOO!
public MyActiveDirImplementation(IMyDatabase database)
{
this._database = database;
}
public ADUserInfo GetUserRecord(string username)
{
return _database.GetSomeUserRecord(username);
}
}
You could, of course, similarly implement your ISessionWrapper or IMyDatabase

Best approach to don't request same info over and over

On my controller I have it inherit a MainController and there I override the Initialize and the OnActionExecuting.
Here I see what is the URL and by that I can check what Client is it, but I learned that for every Method called, this is fired up again and again, even a simple redirectToAction will fire the Initialization of the same controller.
Is there a better technique to avoid this repetition of database call? I'm using Entity Framework, so it will take no time to call the DB as it has the result in cache already, but ... just to know if there is a better technique now in MVC3 rather that host the variables in a Session Variable
sample code
public class MyController : MainController
{
public ActionResult Index()
{
return View();
}
}
public class MainController : Controller
{
public OS_Clients currentClient { get; set; }
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
// get URL Info
string url = requestContext.HttpContext.Request.Url.AbsoluteUri;
string action = requestContext.RouteData.GetRequiredString("action");
string controller = requestContext.RouteData.GetRequiredString("controller");
object _clientUrl = requestContext.RouteData.Values["cliurl"];
if (_clientUrl != null && _clientUrl.ToString() != "none")
{
// Fill up variables
this.currrentClient = db.FindClientById(_clientUrl.ToString());
}
base.Initialize(requestContext);
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// based on client and other variables, redirect to Disable or Login Actions
// ... more code here like:
// filterContext.Result = RedirectToAction("Login", "My");
base.OnActionExecuting(filterContext);
}
}
is it still best to do as:
public OS_Clients currentClient {
get {
OS_Clients _currentClient = null;
if (Session["CurrentClient"] != null)
_currentClient = (OS_Clients)Session["CurrentClient"];
return _currentClient;
}
set {
Session["CurrentClient"] = value;
}
}
It seems that you dealing with application security in that case I would suggest to create Authorization filter, which comes much early into the action. You can put your permission checking code over there and the framework will automatically redirect the user to login page if the permission does not meet AuthorizeCore.
Next, if the user has permission you can use the HttpContext.Items as a request level cache. And then you can create another ActionFilter and in action executing or you can use the base controller to get the user from the Httpcontext.items and assign it to controller property.
If you are using asp.net mvc 3 then you can use the GlobalFilters to register the above mentioned filters instead of decorating each controller.
Hope that helps.
In your base controller, you need to cache the result of the first call in a Session variable.
This makes sure the back-end (DB) is not called unnecessarily, and that the data is bound to the user's Session instead of shared across users, as would be the case with the Application Cache.

Resources