what is the best way to capture page views by person without slowing down performance on the site. I see that stackoverflow show page views all over the place. Are they doing an insert into a db everytime i click on a page?
In asp.net-mvc, Is there any recommended way to track page view per user (my site has a login screen) so i can review which pages people are going to and how often
First off.. if what you really care about is how are customers using my site then you most likely want to look into Google Analytics or a similar service.
But if you want a quick and dirty page view record and you are using ASP.Net MVC 3 then as Chris Fulstow mentioned you're going to want to use a mix of global action filters and caching. Here is an example.
PageViewAttribute.cs
public class PageViewAttribute : ActionFilterAttribute
{
private static readonly TimeSpan pageViewDumpToDatabaseTimeSpan = new TimeSpan(0, 0, 10);
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var calledMethod = string.Format("{0} -> {1}",
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
filterContext.ActionDescriptor.ActionName);
var cacheKey = string.Format("PV-{0}", calledMethod);
var cachedResult = HttpRuntime.Cache[cacheKey];
if(cachedResult == null)
{
HttpRuntime.Cache.Insert(cacheKey, new PageViewValue(), null, DateTime.Now.Add(pageViewDumpToDatabaseTimeSpan) , Cache.NoSlidingExpiration, CacheItemPriority.Default,
onRemove);
}
else
{
var currentValue = (PageViewValue) cachedResult;
currentValue.Value++;
}
}
private static void onRemove(string key, object value, CacheItemRemovedReason reason)
{
if (!key.StartsWith("PV-"))
{
return;
}
// write out the value to the database
}
// Used to get around weird cache behavior with value types
public class PageViewValue
{
public PageViewValue()
{
Value = 1;
}
public int Value { get; set; }
}
}
And in your Global.asax.cs
public class MvcApplication : HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new PageViewAttribute());
}
}
For pre-ASP.Net MVC 3 ONLY you are going to have to apply the same attribute manually to all of your actions.
[PageView]
public ActionResult CallOne()
{
}
[PageView]
public ActionResult CallTwo()
{
}
The best way would probably be a global action filter that intercepts requests to all actions on all controllers, then increments a counter in the database for the current user and page. To save hitting the database too hard, you could cache these values and invalidate them every few minutes, depending on how much traffic you're dealing with.
We use the open source Piwik: http://piwik.org/, which is setup on it's own server. One line of Javascript in the _Layout page makes a call to Piwik after the page has loaded (put the JS at the end) and does not affect page load performance at all.
In addition to just counts, you'll get a ton of info about where your users are coming from, browser, screen resolutions, installed plugins. Plus you can track conversions and use the same tool to track marketing campaigns, etc.
<soapbox>
I cannot think of a situation where you'd be better off implementing this in MVC or in your web app in general. This stuff simply does not belong in your web app and is a meta-concern that should be separated out. This approach has enabled us to track analytics for all of our apps (32 of them: mvc 2/3, webforms, php...) in a unified manner.
If you really don't want to use another tool for this purpose, I would recommend tapping into your IIS log and getting your stats from there. Again, to get any real decision making power out of it, you'll need to put a good analyzer on it. I recommend Splunk: http://www.splunk.com/
</soapbox>
I wanted to post an updated version of Shane's answer for those who are interested. Some things to consider:
You have to set the action attribute up as a service when decorating your
methods using syntax like the following :
[ServiceFilter(typeof(PageViewAttribute))]
As far as I can tell, HttpRuntime.Cache.Insert isn't a thing in .NET Core, so I used a simple implementation of IMemoryCache (You may need to add this line to your startup.cs in order to use the interface):
services.AddMemoryCache();
Because we are injecting IMemoryCache into a class that is not a controller, we need to register our attribute as a service in startup.cs, like so:
services.AddScoped<[PageViewAttribute]>(); - without brackets!
Whatever object you return when creating a cacheKey will be assigned to the 'value' parameter of the OnRemove method.
Below is the code.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controllerActionDescriptor = filterContext.ActionDescriptor as ControllerActionDescriptor;
var arguments = filterContext.ActionArguments;
ActionId = arguments["id"].ToString();
var calledMethod = string.Format("{0} -> {1}",
controllerActionDescriptor.ControllerName,
controllerActionDescriptor.ActionName);
var cacheKey = string.Format("PV-{0}", calledMethod);
var cachedResult = _memoryCache.Get(cacheKey);
if (cachedResult == null)
{
//Get cacheKey if found, if not create cache key with following settings
_memoryCache.GetOrCreate(cacheKey, cacheKey =>
{
cacheKey.AbsoluteExpirationRelativeToNow
= pageViewDumpToDatabaseTimeSpan;
cacheKey.SetValue(1);
cacheKey.RegisterPostEvictionCallback(onRemove);
return cacheKey.Value;
});
}
else
{
_memoryCache.Get(cacheKey);
}
}
//Called when Memory entry is removed
private void onRemove(object key, object value, EvictionReason reason, object state)
{
if (!key.ToString().StartsWith("PV-"))
{
return;
}
// write out the value to the database
SaveToDataBase(key.ToString(), (int)value);
}
As a point of reference, this was done for a .NET Core 5 MVC App.
Regards.
Related
Our company has the need to log certain things each time one of our action methods of our ASP.NET WebApi controllers gets called. Since we use Ninject for the DI right now, we'd like to use it also for this purpose. This is what I have tried so far.
I have Ninject, Ninject.Extensions.Interception and Ninject.Extensions.Interception.DynamicProxy installed through NuGet and I have the following module
public class InterceptAllModule : InterceptionModule
{
public override void Load()
{
Kernel.Intercept(p => p.Request.Service.Name.EndsWith("Controller")).With(new TimingInterceptor());
}
}
Where TimingInterceptor is
public class TimingInterceptor : SimpleInterceptor
{
readonly Stopwatch _stopwatch = new Stopwatch();
protected override void BeforeInvoke(IInvocation invocation)
{
_stopwatch.Start();
}
protected override void AfterInvoke(IInvocation invocation)
{
_stopwatch.Stop();
string message = string.Format("[Execution of {0} took {1}.]",invocation.Request.Method,_stopwatch.Elapsed);
Log.Info(message + "\n");
_stopwatch.Reset();
}
}
Now, when I try to hook the module up with ninject kernel, and run my site
var kernel = new StandardKernel(new InterceptAllModule());
However, whenever there is a call coming in to one of the action method, it throws an error saying
Cannot instantiate proxy of class: MyApiController.
Could someone with experience point out what I'm doing wrong please? Thanks.
Update
So using your Code and Remo's excellent point about needing the action methods to be virtual and putting in an empty default constructor (just to placate dynamic proxy, keep your other constructor still) I have got both the action filter and the interception approach working.
I would say that as it stands your code will intercept potentially unwanted methods on the ApiController so you will probably also need to put some code in place to filter these out e.g. ExecuteAsync and Dispose.
My only other point is performance. Huge disclaimer these are just very basic tests (using the action filter approach each time to log the stats), I invite you to do your own(!)... but using the DynamicProxy interceptor I was getting a time of around 4 milliseconds per get request
[Execution of Get took 00:00:00.0046615.]
[Execution of Get took 00:00:00.0041988.]
[Execution of Get took 00:00:00.0039383.]
Commenting out the Interception code and using an Action filter I was getting sub millisecond performance:
[Execution of Get took 00:00:00.0001146.]
[Execution of Get took 00:00:00.0001116.]
[Execution of Get took 00:00:00.0001364.]
It is up to you whether this is actually an issue or concern but I thought I would point this out.
Previous Response
Have you rulled out using ActionFilters? This is the natural extension point for AOP on an MVC action.
If you were interested in methods other than the actual action on the controller then I would understand but I thought I would post a suggestion anyway.
Inspired by Are ActionFilterAttributes reused across threads? How does that work? and Measure Time Invoking ASP.NET MVC Controller Actions.
Updated to show the exclusion of the timer when method tagged. Inspiration from core WebApi framework specifically AllowAnonymousAttribute and AuthorizeAttribute
Register this globally so that all actions are monitored by this:
GlobalConfiguration.Configuration.Filters.Add(new TimingActionFilter());
Then:
public class TimingActionFilter : ActionFilterAttribute
{
private const string Key = "__action_duration__";
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (SkipLogging(actionContext))
{
return;
}
var stopWatch = new Stopwatch();
actionContext.Request.Properties[Key] = stopWatch;
stopWatch.Start();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (!actionExecutedContext.Request.Properties.ContainsKey(Key))
{
return;
}
var stopWatch = actionExecutedContext.Request.Properties[Key] as Stopwatch;
if(stopWatch != null)
{
stopWatch.Stop();
var actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName;
Debug.Print(string.Format("[Execution of {0} took {1}.]", actionName, stopWatch.Elapsed));
}
}
private static bool SkipLogging(HttpActionContext actionContext)
{
return actionContext.ActionDescriptor.GetCustomAttributes<NoLogAttribute>().Any() ||
actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<NoLogAttribute>().Any();
}
}
And
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)]
public class NoLogAttribute : Attribute
{
}
Now you can exclude the global filter using:
public class ExampleController : ApiController
{
// GET api/example
[NoLog]
public Example Get()
{
//
}
}
For anyone still lurking, the reason I wanted to use Ninject was so I could inject a logger (or anything else) into the interceptor, but I wanted to intercept all actions.
Mark's answer is perfect, but instead of registering globally using
GlobalConfiguration.Configuration.Filters.Add(new TimingActionFilter());
bind your filter with Ninject using
Kernal.BindHttpFilter<TimingActionFilter>(FilterScope.Action).
You'll need to create an appropriate contructor in the TimingActionFilter class.
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.
I want to prevent users submitting forms multiple times in .NET MVC. I've tried several methods using Javascript but have had difficulties getting it to work in all browsers. So, how can I prevent this in my controller? It there some way that multiple submissions can be detected?
Updated answer for ASP.NET Core MVC (.NET Core & .NET 5.0)
Update note: Remember ASP.NET Core is still called "Core" in .NET 5.0.
I'm going to stick to the least-impact use case like before, where you're only adorning those controller actions that you specifically want to prevent duplicate requests on. If you want to have this filter run on every request, or want to use async, there are other options. See this article for more details.
The new form tag helper now automatically includes the AntiForgeryToken so you no longer need to manually add that to your view.
Create a new ActionFilterAttribute like this example. You can do many additional things with this, for example including a time delay check to make sure that even if the user presents two different tokens, they aren't submitting multiple times per minute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class PreventDuplicateRequestAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext context) {
if (context.HttpContext.Request.HasFormContentType && context.HttpContext.Request.Form.ContainsKey("__RequestVerificationToken")) {
var currentToken = context.HttpContext.Request.Form["__RequestVerificationToken"].ToString();
var lastToken = context.HttpContext.Session.GetString("LastProcessedToken");
if (lastToken == currentToken) {
context.ModelState.AddModelError(string.Empty, "Looks like you accidentally submitted the same form twice.");
}
else {
context.HttpContext.Session.SetString("LastProcessedToken", currentToken);
}
}
}
}
By request, I also wrote an asynchronous version which can be found here.
Here's a contrived usage example of the custom PreventDuplicateRequest attribute.
[HttpPost]
[ValidateAntiForgeryToken]
[PreventDuplicateRequest]
public IActionResult Create(InputModel input) {
if (ModelState.IsValid) {
// ... do something with input
return RedirectToAction(nameof(SomeAction));
}
// ... repopulate bad input model data into a fresh viewmodel
return View(viewModel);
}
A note on testing: simply hitting back in a browser does not use the same AntiForgeryToken. On faster computers where you can't physically double click the button twice, you'll need to use a tool like Fiddler to replay your request with the same token multiple times.
A note on setup: Core MVC does not have sessions enabled by default. You'll need to add the Microsoft.AspNet.Session package to your project, and configure your Startup.cs properly. Please read this article for more details.
Short version of Session setup is:
In Startup.ConfigureServices() you need to add:
services.AddDistributedMemoryCache();
services.AddSession();
In Startup.Configure() you need to add (before app.UseMvc() !!):
app.UseSession();
Original answer for ASP.NET MVC (.NET Framework 4.x)
First, make sure you're using the AntiForgeryToken on your form.
Then you can make a custom ActionFilter:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class PreventDuplicateRequestAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if (HttpContext.Current.Request["__RequestVerificationToken"] == null)
return;
var currentToken = HttpContext.Current.Request["__RequestVerificationToken"].ToString();
if (HttpContext.Current.Session["LastProcessedToken"] == null) {
HttpContext.Current.Session["LastProcessedToken"] = currentToken;
return;
}
lock (HttpContext.Current.Session["LastProcessedToken"]) {
var lastToken = HttpContext.Current.Session["LastProcessedToken"].ToString();
if (lastToken == currentToken) {
filterContext.Controller.ViewData.ModelState.AddModelError("", "Looks like you accidentally tried to double post.");
return;
}
HttpContext.Current.Session["LastProcessedToken"] = currentToken;
}
}
}
And on your controller action you just...
[HttpPost]
[ValidateAntiForgeryToken]
[PreventDuplicateRequest]
public ActionResult CreatePost(InputModel input) {
...
}
You'll notice this doesn't prevent the request altogether. Instead it returns an error in the modelstate, so when your action checks if ModelState.IsValid then it will see that it is not, and will return with your normal error handling.
I've tried several methods using Javascript but have had difficulties getting it to work in all browsers
Have you tried using jquery?
$('#myform').submit(function() {
$(this).find(':submit').attr('disabled', 'disabled');
});
This should take care of the browser differences.
Just to complete the answer of #Darin, if you want to handle the client validation (if the form has required fields), you can check if there's input validation error before disabling the submit button :
$('#myform').submit(function () {
if ($(this).find('.input-validation-error').length == 0) {
$(this).find(':submit').attr('disabled', 'disabled');
}
});
What if we use $(this).valid()?
$('form').submit(function () {
if ($(this).valid()) {
$(this).find(':submit').attr('disabled', 'disabled');
}
});
Strategy
The truth is that you need several lines of attack for this problem:
The Post/Redirect/Get (PRG) pattern is not enough by itself. Still, it should always be used to provide the user with good experiences when using back, refresh, etc.
Using JavaScript to prevent the user from clicking the submit button multiple times is a must because it provides a much less jarring user experience compared to server-side solutions.
Blocking duplicate posts solely on the client side doesn't protect against bad actors and does not help with transient connection problems. (What if your first request made it to the server but the response did not make it back to the client, causing your browser to automatically resend the request?)
I'm not going to cover PRG, but here are my answers for the other two topics. They build upon the other answers here. FYI I'm using .NET Core 3.1.
Client-Side
Assuming you are using jQuery validation, I believe this is the cleanest/most efficient way to prevent your form submit button from being double-clicked. Note that submitHandler is only called after validation has passed, so there is no need to re-validate.
$submitButton = $('#submitButton');
$('#mainForm').data('validator').settings.submitHandler = function (form) {
form.submit();
$submitButton.prop('disabled', true);
};
An alternative to disabling the submit button is to show an overlay in front of the form during submission to 1) block any further interaction with the form and 2) communicate that the page is "doing something." See this article for more detail.
Server-Side
I started off with Jim Yarbro's great answer above, but then I noticed Mark Butler's answer pointing out how Jim's method fails if someone submits forms via multiple browser tabs (because each tab has a different token and posts from different tabs can be interlaced). I confirmed that such a problem really does exist and then decided to upgrade from tracking just the last token to tracking the last x tokens.
To facilitate that, I made a couple of helper classes: one for storing the last x tokens and one for making it easy to store/retrieve objects to/from session storage. The main code now checks that the current token is not found in the token history. Other than that, the code is pretty much the same. I just made some little tweaks to suit my tastes. I included both the regular and asynchronous versions. The full code is below, but these are the critical lines:
var history = session.Get<RotatingHistory<string>>(HistoryKey) ?? new RotatingHistory<string>(HistoryCapacity);
if (history.Contains(token))
{
context.ModelState.AddModelError("", DuplicateSubmissionErrorMessage);
}
else
{
history.Add(token);
}
Sadly, the fatal flaw of this approach is that the feedback from the first post (before any duplicates) gets lost. A better (but much more complex) solution would be to store the result of each unique request by GUID, and then handle duplicate requests by not only skipping doing the work again but also returning the same result from the first request, giving the user a seamless experience. This thorough article detailing Air BnB's methods of avoiding duplicate payments will give you an idea of the concepts.
PreventDuplicateFormSubmissionAttribute.cs
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
// This class provides an attribute for controller actions that flags duplicate form submissions
// by adding a model error if the request's verification token has already been seen on a prior
// form submission.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class PreventDuplicateFormSubmissionAttribute: ActionFilterAttribute
{
const string TokenKey = "__RequestVerificationToken";
const string HistoryKey = "RequestVerificationTokenHistory";
const int HistoryCapacity = 5;
const string DuplicateSubmissionErrorMessage =
"Your request was received more than once (either due to a temporary problem with the network or a " +
"double button press). Any submissions after the first one have been rejected, but the status of the " +
"first one is unclear. It may or may not have succeeded. Please check elsewhere to verify that your " +
"request had the intended effect. You may need to resubmit it.";
public override void OnActionExecuting(ActionExecutingContext context)
{
HttpRequest request = context.HttpContext.Request;
if (request.HasFormContentType && request.Form.ContainsKey(TokenKey))
{
string token = request.Form[TokenKey].ToString();
ISession session = context.HttpContext.Session;
var history = session.Get<RotatingHistory<string>>(HistoryKey) ?? new RotatingHistory<string>(HistoryCapacity);
if (history.Contains(token))
{
context.ModelState.AddModelError("", DuplicateSubmissionErrorMessage);
}
else
{
history.Add(token);
session.Put(HistoryKey, history);
}
}
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
HttpRequest request = context.HttpContext.Request;
if (request.HasFormContentType && request.Form.ContainsKey(TokenKey))
{
string token = request.Form[TokenKey].ToString();
ISession session = context.HttpContext.Session;
await session.LoadAsync();
var history = session.Get<RotatingHistory<string>>(HistoryKey) ?? new RotatingHistory<string>(HistoryCapacity);
if (history.Contains(token))
{
context.ModelState.AddModelError("", DuplicateSubmissionErrorMessage);
}
else
{
history.Add(token);
session.Put(HistoryKey, history);
await session.CommitAsync();
}
await next();
}
}
}
RotatingHistory.cs
using System.Linq;
// This class stores the last x items in an array. Adding a new item overwrites the oldest item
// if there is no more empty space. For the purpose of being JSON-serializable, its data is
// stored via public properties and it has a parameterless constructor.
public class RotatingHistory<T>
{
public T[] Items { get; set; }
public int Index { get; set; }
public RotatingHistory() {}
public RotatingHistory(int capacity)
{
Items = new T[capacity];
}
public void Add(T item)
{
Items[Index] = item;
Index = ++Index % Items.Length;
}
public bool Contains(T item)
{
return Items.Contains(item);
}
}
SessonExtensions.cs
using System.Text.Json;
using Microsoft.AspNetCore.Http;
// This class is for storing (serializable) objects in session storage and retrieving them from it.
public static class SessonExtensions
{
public static void Put<T>(this ISession session, string key, T value) where T : class
{
session.SetString(key, JsonSerializer.Serialize(value));
}
public static T Get<T>(this ISession session, string key) where T : class
{
string s = session.GetString(key);
return s == null ? null : JsonSerializer.Deserialize<T>(s);
}
}
You could include a hidden (random or counter) value in the form post, a controller could track these values in an 'open' list or something similar; every time your controller hands out a form it embeds a value, which it tracks allowing one post use of it.
In its self, no, however depending on what the controller is actually doing, you should be able to work out a way.
Is a record being created in the database that you can check for to see if they've already submitted the form?
Just add this code at the end of your page. I am using "jquery-3.3.1.min.js" and "bootstrap 4.3.1"
<script type="text/javascript">
$('form').submit(function () {
if ($(this).valid()) {
$(this).find(':submit').attr('disabled', 'disabled');
}
});
</script>
Use the Post/Redirect/Get design pattern.
PS:
It looks to me that the answer by Jim Yarbro could have a fundamental flaw in that the __RequestVerificationToken stored in the HttpContext.Current.Session["LastProcessedToken"] will be replaced when a second form is submitted (from say another browser window). At this point, it is possible to re-submit the first form without it being recognized as a duplicate submission. For the proposed model to work, wouldn’t a history of __RequestVerificationToken be required? This doesn't seem feasible.
Dont reinvent the wheel :)
Use the Post/Redirect/Get design pattern.
Here you can find a question and an answer giving some suggestions on how to implement it in ASP.NET MVC.
You can also pass some sort of token in a hidden field and validate this in the controller.
Or you work with redirects after submitting values. But this get's difficult if you take heavily advantage of ajax.
This works on every browser
document.onkeydown = function () {
switch (event.keyCode) {
case 116: //F5 button
event.returnValue = false;
event.keyCode = 0;
return false;
case 82: //R button
if (event.ctrlKey) {
event.returnValue = false;
event.keyCode = 0;
return false;
}
}
}
You can do this by creating some sort of static entry flag that is user specific, or specific to whatever way you want to protect the resource. I use a ConcurrentDictionary to track entrance. The key is basically the name of the resource I'm protecting combined with the User ID. The trick is figuring out how to block the request when you know it's currently processing.
public async Task<ActionResult> SlowAction()
{
if(!CanEnterResource(nameof(SlowAction)) return new HttpStatusCodeResult(204);
try
{
// Do slow process
return new SlowProcessActionResult();
}
finally
{
ExitedResource(nameof(SlowAction));
}
}
Returning a 204 is a response to the double-click request that will do nothing on the browser side. When the slow process is done, the browser will receive the correct response for the original request and act accordingly.
Use this simple jquery input field and will work awesomely even if you have multiple submit buttons in a single form.
$('input[type=submit]').click(function () {
var clickedBtn = $(this)
setTimeout(function () {
clickedBtn.attr('disabled', 'disabled');
}, 1);
});
I have an ASP.NET MVC app which depends on a lot of settings (name-value pairs), I am planning to store this information in a database table called SiteSettings. Is there an easy way in which I can get these settings using NHibernate. And what are the best practices when saving settings for a web application. And by settings I mean the settings which control the flow of processes in the web application and which are governed by business rules. These are not the typical connection string kind of settings. I was unable to get much information on the web on this topic. Maybe I am not searching on the right keywords, Any help will be greatly appreciated.
I can't answer in the context of nhibernate (which I'm not using) or best practices (I came up with this on my own recently). However, it works well for me, and will probably work for you.
I have a table (Biz_Config) in the database to store business preferences. (I've created a web.config section for what I call IT preferences.)
I have a class that is in charge of managing the biz preferences. The constructor grabs the entire table (one row per setting) and copies these into a dictionary, and it has methods to access (such as bizconfig.get("key")) and update this dictionary, also updating the table at the same time. It also has a few shortcut properties for specific dictionary values, especially where the value has to be cast (I have a few important numbers). It works quite well.
In order to be more efficient and not instantiate it every time I need a setting, and also to access it easily from my controllers and views, I created a static class, Globals, that is in charge of getting things out of the session or application variables. For the biz config object, it checks the application variable and, if null, creates a new one. Otherwise it just returns it. Globals is part of my helpers namespace, which is included in my web.config to be available to my views. So I can easily call:
<% Globals.Biz_Config.Get("key") %>
I hope this helps. If you'd like code, I can dig that up for you.
James
If you have a set of key/value pairs, you probably want to use a <map>. See the official NHibernate documentation or Ayende's post about 'NHibernate Mapping - <map/>'.
I have come up with a solution which is quite similar to the one suggested by James. I have an SiteSettingsService class which manages the settings for the whole site, it has a simple dependency on an interface called ISiteServiceRepository. This might not be the most elegant solution, But it is working perfectly for me. I have also configured the SiteSettingsService class as a Singleton using StructureMap. So, it saves me unnecessary instantiantion every time I need any settings.
//ISiteServiceRepository, an implementation of this uses NHibernate to do just two things
//i)Get all the settings, ii)Persist all the settings
using System.Collections.Generic;
using Cosmicvent.Mcwa.Core.Domain.Model;
namespace Cosmicvent.Mcwa.Core.Domain {
public interface ISiteServiceRepository {
IList<Setting> GetSettings();
void PersistSettings(IDictionary<string, string> settings);
}
}
//The main SiteSettingsService class depends on the ISiteServiceRepository
using System;
using System.Collections.Generic;
using Cosmicvent.Mcwa.Core.Domain;
using Cosmicvent.Mcwa.Core.Domain.Model;
namespace Cosmicvent.Mcwa.Core.Services {
public class SiteSettingsService : ISiteSettingsService {
private readonly ISiteServiceRepository _siteServiceRepository;
private IDictionary<string, string> _settings;
public SiteSettingsService(ISiteServiceRepository siteServiceRepository) {
_siteServiceRepository = siteServiceRepository;
//Fill up the settings
HydrateSettings();
}
public int ActiveDegreeId {
get {
return int.Parse(GetValue("Active_Degree_Id"));
}
}
public string SiteTitle {
get { return GetValue("Site_Title"); }
}
public decimal CounsellingFee {
get { return decimal.Parse(GetValue("Counselling_Fee")); }
}
public decimal TuitionFee {
get { return decimal.Parse(GetValue("Tuition_Fee")); }
}
public decimal RegistrationFee {
get { return decimal.Parse(GetValue("Registration_Fee")); }
}
public void UpdateSetting(string setting, string value) {
if (!string.IsNullOrEmpty(setting) && !string.IsNullOrEmpty(value)) {
SetValue(setting, value);
PersistSettings();
}
}
//Helper methods
private void HydrateSettings() {
_settings = new Dictionary<string, string>();
IList<Setting> siteRepoSettings = _siteServiceRepository.GetSettings();
if (siteRepoSettings == null) {
throw new ArgumentException("Site Settings Repository returned a null dictionary");
}
foreach (Setting setting in siteRepoSettings) {
_settings.Add(setting.Name.ToUpper(), setting.Value);
}
}
private string GetValue(string key) {
key = key.ToUpper();
if (_settings == null) {
throw new NullReferenceException("The Site Settings object is Null");
}
if (!_settings.ContainsKey(key)) {
throw new KeyNotFoundException(string.Format("The site setting {0} was not found", key));
}
return _settings[key];
}
private void SetValue(string key, string value) {
key = key.ToUpper();
if (_settings == null) {
throw new NullReferenceException("The Site Settings object is Null");
}
if (!_settings.ContainsKey(key)) {
throw new KeyNotFoundException(string.Format("The site setting {0} was not found", key));
}
_settings[key] = value;
}
private void PersistSettings() {
_siteServiceRepository.PersistSettings(_settings);
}
}
}
Hope this helps future developers facing similar problems. Any suggestions for improving this are more than welcome.
I am writing a web application that will allow a user to browse to multiple web pages within the website making certain requests. All information that the user inputs will be stored in an object that I created. The problem is that I need this object to be accessed from any part of the website and I don't really know the best way to accomplish this. I know that one solution is to use session variables but I don't know how to use them in asp .net MVC. And where would I declare a session variable? Is there any other way?
I would think you'll want to think about if things really belong in a session state. This is something I find myself doing every now and then and it's a nice strongly typed approach to the whole thing but you should be careful when putting things in the session context. Not everything should be there just because it belongs to some user.
in global.asax hook the OnSessionStart event
void OnSessionStart(...)
{
HttpContext.Current.Session.Add("__MySessionObject", new MySessionObject());
}
From anywhere in code where the HttpContext.Current property != null you can retrive that object. I do this with an extension method.
public static MySessionObject GetMySessionObject(this HttpContext current)
{
return current != null ? (MySessionObject)current.Session["__MySessionObject"] : null;
}
This way you can in code
void OnLoad(...)
{
var sessionObj = HttpContext.Current.GetMySessionObject();
// do something with 'sessionObj'
}
The answer here is correct, I however struggled to implement it in an ASP.NET MVC 3 app. I wanted to access a Session object in a controller and couldn't figure out why I kept on getting a "Instance not set to an instance of an Object error". What I noticed is that in a controller when I tried to access the session by doing the following, I kept on getting that error. This is due to the fact that this.HttpContext is part of the Controller object.
this.Session["blah"]
// or
this.HttpContext.Session["blah"]
However, what I wanted was the HttpContext that's part of the System.Web namespace because this is the one the Answer above suggests to use in Global.asax.cs. So I had to explicitly do the following:
System.Web.HttpContext.Current.Session["blah"]
this helped me, not sure if I did anything that isn't M.O. around here, but I hope it helps someone!
Because I dislike seeing "HTTPContext.Current.Session" about the place, I use a singleton pattern to access session variables, it gives you an easy to access strongly typed bag of data.
[Serializable]
public sealed class SessionSingleton
{
#region Singleton
private const string SESSION_SINGLETON_NAME = "Singleton_502E69E5-668B-E011-951F-00155DF26207";
private SessionSingleton()
{
}
public static SessionSingleton Current
{
get
{
if ( HttpContext.Current.Session[SESSION_SINGLETON_NAME] == null )
{
HttpContext.Current.Session[SESSION_SINGLETON_NAME] = new SessionSingleton();
}
return HttpContext.Current.Session[SESSION_SINGLETON_NAME] as SessionSingleton;
}
}
#endregion
public string SessionVariable { get; set; }
public string SessionVariable2 { get; set; }
// ...
then you can access your data from anywhere:
SessionSingleton.Current.SessionVariable = "Hello, World!";
Well, IMHO..
never reference a Session inside your view/master page
minimize your useage of Session. MVC provides TempData obj for this, which is basically a Session that lives for a single trip to the server.
With regards to #1, I have a strongly typed Master View which has a property to access whatever the Session object represents....in my instance the stongly typed Master View is generic which gives me some flexibility with regards to strongly typed View Pages
ViewMasterPage<AdminViewModel>
AdminViewModel
{
SomeImportantObjectThatWasInSession ImportantObject
}
AdminViewModel<TModel> : AdminViewModel where TModel : class
{
TModel Content
}
and then...
ViewPage<AdminViewModel<U>>
If you are using asp.net mvc, here is a simple way to access the session.
From a Controller:
{Controller}.ControllerContext.HttpContext.Session["{name}"]
From a View:
<%=Session["{name}"] %>
This is definitely not the best way to access your session variables, but it is a direct route. So use it with caution (preferably during rapid prototyping), and use a Wrapper/Container and OnSessionStart when it becomes appropriate.
HTH
Although I don't know about asp.net mvc, but this is what we should do in a normal .net website. It should work for asp.net mvc also.
YourSessionClass obj=Session["key"] as YourSessionClass;
if(obj==null){
obj=new YourSessionClass();
Session["key"]=obj;
}
You would put this inside a method for easy access.
HTH
There are 3 ways to do it.
You can directly access HttpContext.Current.Session
You can Mock HttpContextBase
Create a extension method for HttpContextBase
I prefer 3rd way.This link is good reference.
Get/Set HttpContext Session Methods in BaseController vs Mocking HttpContextBase to create Get/Set methods
My way of accessing sessions is to write a helper class which encapsulates the various field names and their types. I hope this example helps:
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.SessionState;
namespace dmkp
{
/// <summary>
/// Encapsulates the session state
/// </summary>
public sealed class LoginInfo
{
private HttpSessionState _session;
public LoginInfo(HttpSessionState session)
{
this._session = session;
}
public string Username
{
get { return (this._session["Username"] ?? string.Empty).ToString(); }
set { this._session["Username"] = value; }
}
public string FullName
{
get { return (this._session["FullName"] ?? string.Empty).ToString(); }
set { this._session["FullName"] = value; }
}
public int ID
{
get { return Convert.ToInt32((this._session["UID"] ?? -1)); }
set { this._session["UID"] = value; }
}
public UserAccess AccessLevel
{
get { return (UserAccess)(this._session["AccessLevel"]); }
set { this._session["AccessLevel"] = value; }
}
}
}
Great answers from the guys but I would caution you against always relying on the Session. It is quick and easy to do so, and of course would work but would not be great in all cicrumstances.
For example if you run into a scenario where your hosting doesn't allow session use, or if you are on a web farm, or in the example of a shared SharePoint application.
If you wanted a different solution you could look at using an IOC Container such as Castle Windsor, creating a provider class as a wrapper and then keeping one instance of your class using the per request or session lifestyle depending on your requirements.
The IOC would ensure that the same instance is returned each time.
More complicated yes, if you need a simple solution just use the session.
Here are some implementation examples below out of interest.
Using this method you could create a provider class along the lines of:
public class CustomClassProvider : ICustomClassProvider
{
public CustomClassProvider(CustomClass customClass)
{
CustomClass = customClass;
}
public string CustomClass { get; private set; }
}
And register it something like:
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<ICustomClassProvider>().UsingFactoryMethod(
() => new CustomClassProvider(new CustomClass())).LifestylePerWebRequest());
}
You can use ViewModelBase as base class for all models , this class will take care of pulling data from session
class ViewModelBase
{
public User CurrentUser
{
get { return System.Web.HttpContext.Current.Session["user"] as User };
set
{
System.Web.HttpContext.Current.Session["user"]=value;
}
}
}
You can write a extention method on HttpContextBase to deal with session data
T FromSession<T>(this HttpContextBase context ,string key,Action<T> getFromSource=null)
{
if(context.Session[key]!=null)
{
return (T) context.Session[key];
}
else if(getFromSource!=null)
{
var value = getFromSource();
context.Session[key]=value;
return value;
}
else
return null;
}
Use this like below in controller
User userData = HttpContext.FromSession<User>("userdata",()=> { return user object from service/db });
The second argument is optional it will be used fill session data for that key when value is not present in session.