I am using the UserData Property of the FormsAuthenticationTicket to store some userspecific information. I have a HelperClass which deserializes this UserData into a custom Object for strongly-typed access. I have my controller setup as follows
public class SomeController : Controller
{
private CookieData _cookieData;
public SomeController()
{
_service = new ForderungsStellerService(new ModelStateWrapper(this.ModelState));
HttpCookie cookie = Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
_cookieData= GetSessionData.FromCookie(ticket);
}
}
The problem seems to be, that Request is null at controller construction time. When accessing Request.Cookies from an ActionMethod, this snippet is working.
I would like to have the _cookieData object be filled within the constructor for DRY reasons.
Does anyone has a hint on that problem?
best regards...
I would create a ModelBinder that understands CookieData and how to get it out of the Request object. I fear the unit-test creation code necessary to make the constructor happy. If you take it as a parameter to the controller with a Model Binder you can avoid that test overhead.
public class SomeController : Controller
{
// only need to pass in the data object for unit testing.
// ModelBinder takes care of DRY
public ActionResult Index(CookieData cookieData)
{
}
}
The answer to why it doesn't work in the constructor is that the Controller hasn't been initialized with the ControllerContext at that point.
public HttpContextBase HttpContext {
get {
return ControllerContext == null
? null
: ControllerContext.HttpContext;
}
}
If you really want to do it in the constructor (don't) then use HttpContext.Request instead of the wrapper. But by doing so you will have made your code untestable and your alignment will drop by 3 points.
Override Controller.Initialize():
protected override void Initialize(RequestContext requestContext) {
base.Initialize(requestContext);
// do further initialization here
}
Properties like Request, etc. will be available to you after the call to base.Initialize().
Its good to be DRY but in case of ASP.NET MVC it most often means using a custom filter attribute or like talljoe showed a Model Binder.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpCookie cookie = filterContext.HttpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
filterContext.ActionParameters["CookieData"] = GetSessionData.FromCookie(ticket);
base.OnActionExecuting(filterContext);
}
Related
I'm new to MVC (5). In order to add localization support to my website I added a "Language" field to my ApplicationUser : IdentityUser
What's the best approach to now store this information in the browser and ensure that it gets re-created even if the user manually deletes it?
TL; but I've got time
What I've tried until now:
I started creating a cookie in my method private async Task SignInAsync(ApplicationUser user, bool isPersistent) but I notice that:
This method is not used if the user is already authenticated and automatically logs in using the .Aspnet.Applicationcookie and my language cookie could be meanwhile expired (or been deleted).
A user could manually delete the cookie, just for fun.
I thought about checking its existence in the controller (querying the logged user and getting it from the db) and it works but I'd need to do it in EVERY controller. I'm not sure is the correct way to do this.
Any suggestion about how to approach this problem and guarantee that the application has a valid "language cookie" on every request?
It sounds to me like what you want here is a Custom Action Filter. You can override the OnActionExecuting method which means the logic is run before any action is called
public class EnsureLanguagePreferenceAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var langCookie = filterContext.HttpContext.Request.Cookies["LanguagePref"];
if (langCookie == null)
{
// cookie doesn't exist, either pull preferred lang from user profile
// or just setup a cookie with the default language
langCookie = new HttpCookie("LanguagePref", "en-gb");
filterContext.HttpContext.Request.Cookies.Add(langCookie);
}
// do something with langCookie
base.OnActionExecuting(filterContext);
}
}
Then register your attribute globally so it just becomes the default behaviour on every controller action
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new EnsureLanguagePreferenceAttribute());
}
To me, the easiest way would be to create your own Authorize attribute (since your language options are tied to an authenticated user account). Inside of your new authorize attribute, simply perform the check if the cookie exists. If it does, then life is good. Else, query the user's database profile and reissue the cookie with the stored value
public class MyAuthorization : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//no point in cookie checking if they are not authorized
if(!base.AuthorizeCore(httpContext)) return false;
var cookie = httpContext.Request.Cookies["LanguageCookie"];
if (cookie == null) {
CreateNewCookieMethod();
}
return true;
}
}
To use, replace [Authorize] with [MyAuthorization] in your project.
If you don't want to mess with the [Authorize] attribute, you could create your own attribute that does the cookie checking and decorate your controller with that one as well.
One last alternative is to create your own Controller class that does the checking on the OnActionExecuting.
public class MyBaseController : Controller
{
public string Language {get;set;}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var cookie = filterContext.HttpContext.Request.Cookies["LanguageCookie"];
if(cookie == null){
cookie = CreateNewCookieMethod();
filterContext.HttpContext.Request.Cookies.Add(cookie);
}
Language = cookie.Value;
base.OnActionExecuting(filterContext);
}
How to use (note that we inherit from MybaseController now)
public class HomeController : MyBaseController{
public ActionResult Index(){
//Language comes from the base controller class
ViewBag.Language = Language;
Return View();
}
}
This method is neat because now that Language variable will be available in any controller that inherits from this new class.
Either of these will give you a single, cookie checking point. Additionally, you are only going back to the database only in the instance that the cookie does not exist.
When a Record gets updated in my Database, I need to be able to save who edited it.
Currently in my Repository I do this
pt.ModifiedBy = HttpContext.Current.User.Identity.Name;
There has to be a better way of doing this or is this only method?
By using HttpContext.Current.User you're tightly coupling your DbContext with HttpContext which is not a good idea in case you'll expose your DbContext to a non-web environment (UnitTesting, WCF, WPF etc).
You can use System.Security.Principal.IIdentity instead, just like exposed in ASP.NET (System.Web.HttpContext.Current.User.Identity), WCF (System.ServiceModel.OperationContext.Current.ServiceSecurityContext.PrimaryIdentity) and Thread (Thread.CurrentPrincipal.Identity).
Then, have your DbContext accept IIdentity in its constructor, and whenever the context initialized pass the appropriate IIdentity (from your current context).
For example (based on #qujck answer):
public class MyContext : DbContext
{
private readonly IIdentity _identity;
public DbContext(IIdentity identity)
{
this._identity = identity;
}
public override int SaveChanges()
{
//you may need this line depending on your exact configuration
//ChangeTracker.DetectChanges();
foreach (DbEntityEntry o in GetChangedEntries())
{
IEntity entity = o.Entity as IEntity;
entity.ModifiedBy = this._identity.Name;
}
return base.SaveChanges();
}
}
// Usage (ASP.NET):
var context = new DbContext(System.Web.HttpContext.Current.User.Identity);
IMO the best option is to handle all the auditing in one place - your unit of work (DbContext). This is easily achieved by having all of your Poco objects implement a common interface, such as IEntity.
Here's an example:
public class MyContext : DbContext
{
public override int SaveChanges()
{
//you may need this line depending on your exact configuration
//ChangeTracker.DetectChanges();
foreach (DbEntityEntry o in GetChangedEntries())
{
IEntity entity = o.Entity as IEntity;
entity.ModifiedBy = HttpContext.Current.User.Identity.Name;
}
return base.SaveChanges();
}
private IEnumerable<DbEntityEntry> GetChangedEntries()
{
return new List<DbEntityEntry>(
from e in ChangeTracker.Entries()
where e.State != System.Data.EntityState.Unchanged
select e);
}
}
I am creating an ActionResult in ASP.Net MVC to serve images. With Session state enabled, IIS will only handle one request at a time from the same user. (This is true not just in MVC.)
Therefore, on a page with multiple images calling back to this Action, only one image request can be handled at a time. It's synchronous.
I'd like this image Action to be asynchronous -- I'd like multiple image requests to each execute without needing the previous one to complete. (If the images were just static files, IIS would serve them up this way.)
So, I'd like to disable Session just for calls to that Action, or to specify that certain requests do not have Session state. Anyone know how this is done in MVC? Thanks!
If anyone is in the situation I was in, where your image controller actually needs read only access to the session, you can put the SessionState attribute on your controller
[SessionState(SessionStateBehavior.ReadOnly)]
See http://msdn.microsoft.com/en-us/library/system.web.mvc.sessionstateattribute.aspx for more info.
Thanks to https://stackoverflow.com/a/4235006/372926
Rather than implementing an action filter for this, why don't you implement a RouteHandler?
Here's the deal - IRouteHandler has one method - GetHttpHandler. When you make an ASP.Net MVC request to a controller, by default the routing engine handles the request by creating a new instance of MvcRouteHandler, which returns an MvcHandler. MvcHandler is an implementation of IHttpHandler which is marked with the (surprise!) IRequiresSessionState interface. This is why a normal request uses Session.
If you follow my blog post on how to implement a custom RouteHandler (instead of using MvcRouteHandler) for serving up images - you can skip returning a session-tagged IHttpHandler.
This should free IIS from imposing synchronicity on you. It would also likely be more performant because it's skipping all the layers of the MVC code dealing with filters.
I also came across the same problem and after doing R&D this link worked for me
Reference:
https://techatfingers.wordpress.com/2016/06/14/session-state-on-action/
Create custom Attribute
Override the “GetControllerSessionBehavior” method present in class DefaultControllerFactory.
Register it in global.aspx
1> Create custom Attribute
public sealed class ActionSessionStateAttribute : Attribute
{
public SessionStateBehavior SessionBehavior { get; private set; }
public ActionSessionStateAttribute(SessionStateBehavior sessionBehavior)
{
SessionBehavior = sessioBehavior;
}
}
2. Override
public class SessionControllerFactory : DefaultControllerFactory
{
protected override SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
return SessionStateBehavior.Default;
var actionName = requestContext.RouteData.Values["action"].ToString();
Type typeOfRequest=requestContext.HttpContext.Request.RequestType.ToLower() =="get"?typeof(HttpGetAttribute):typeof(HttpPostAttribute);
// [Line1]
var cntMethods = controllerType.GetMethods()
.Where(m =>
m.Name == actionName &&
( ( typeOfRequest == typeof(HttpPostAttribute) &&
m.CustomAttributes.Where(a => a.AttributeType == typeOfRequest).Count()>0
)
||
( typeOfRequest == typeof(HttpGetAttribute) &&
m.CustomAttributes.Where(a => a.AttributeType == typeof(HttpPostAttribute)).Count() == 0
)
)
);
MethodInfo actionMethodInfo = actionMethodInfo = cntMethods != null && cntMethods.Count() == 1 ? cntMethods.ElementAt(0):null;
if (actionMethodInfo != null)
{
var sessionStateAttr = actionMethodInfo.GetCustomAttributes(typeof(ActionSessionStateAttribute), false)
.OfType<ActionSessionStateAttribute>()
.FirstOrDefault();
if (sessionStateAttr != null)
{
return sessionStateAttr.Behavior;
}
}
return base.GetControllerSessionBehavior(requestContext, controllerType);
}
3. Register class in Global.asax
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// --- other code ---
ControllerBuilder.Current.SetControllerFactory(typeof(SessionControllerFactory));
}
}
Try serving the images from another domain. So something like images.mysite.com.
This will provide you two benefits: One, sessions are tracked by a cookie, so images.mysite.com won't have the cookie. Two, it will give you an additional two concurrent requests to retrieve images.
Have you considered setting up a HttpHandler to serve up your images?
SessionState attribute is quite helpful if u use mvc3. How to achieve this with mvc2 needs a little more coding.
Idea is to tell the asp.net that specific request wont use session object.
So, Create a custom route handler for specific requests
public class CustomRouteHandler : IRouteHandler
{
public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.HttpContext.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.ReadOnly);
return new MvcHandler(requestContext);
}
}
SessionStateBehavior enum has 4 members, you should use "disabled" or "readonly" modes to get async behavior.
After creating this custom route handler, be sure that your specific requests goes through this handler. This can be done via defining new routes at Global.asax
routes.Add("Default", new Route(
"{controller}/{action}",
new RouteValueDictionary(new { controller = "Home", action = "Index"}),
new CustomRouteHandler()
));
Adding this route makes all your requests to be handled by your custom route handler class. You can make it specific by defining different routes.
Change DefaultCOntrollerFactory to custom ControllerFactory class. Default Controller.TempDataProvider use SessionStateTempDataProvider. you can change it.
1.Set web.config/system.web/sessionState:mode="Off".
2.create DictionaryTempDataProvider class.
public class DictionaryTempDataProvider : ITempDataProvider
{
public IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
{
return new Dictionary<string, object>();
}
public void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
{
}
}
3.Create DictionaryTempDataControllerFactory
public class DictionaryTempDataControllerFactory : DefaultControllerFactory
{
public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
var controller = base.CreateController(requestContext, controllerName) as Controller;
if (controller!=null)
controller.TempDataProvider = new DictionaryTempDataProvider();
return controller;
}
}
4.In global.asax.cs Apprication_Start event set DictionaryTempDataControllerFactory.
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(
new DictionaryTempDataControllerFactory()
);
}
On our server, IIS doesn't even know about sessions - it's the ASP.NET stack that handles one request per session at a time. Static files, like images, are never affected.
Is it possible that your ASP.NET app is serving the files instead of IIS?
Create new Controller
Decorate controler with [SessionState(SessionStateBehavior.Disabled)]
Refactor code you want seesion stated disabled for to that controller
I would to share my solution for disable ASP.NET Session for an specific request (in my case, a WCF Service) using an HttpModule:
public class AspNetSessionFilterModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PostMapRequestHandler += OnPostMapRequestHandler;
}
private void OnPostMapRequestHandler(object sender, EventArgs e)
{
var context = (sender as HttpApplication).Context;
DisableSessionForSomeRequests(context);
}
private void DisableSessionForSomeRequests(HttpContext context)
{
if ("~/Services/MyService.svc".Equals(context.Request.AppRelativeCurrentExecutionFilePath, StringComparison.InvariantCultureIgnoreCase))
{
context.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Disabled);
}
}
public void Dispose()
{ }
}
I have the pattern User/{domain}/{username} set up via Routing. Everything works except for one thing. I can't figure out how to get the domain and username variables passed to my redirected page. Below is my GetHttpHandler method from my IRouteHandler implementation.
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string basePath;
basePath = "~/UserPage.aspx";
string domain = requestContext.RouteData.GetRequiredString("domain");
string username = requestContext.RouteData.GetRequiredString("username");
string virtualPath =
string.Format(basePath + "?domain={0}&username={1}", domain, username);
return (Page)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page));
}
I get the error from the last line of code:
UserPage.aspx?domain=SOMEDOMAIN&username=SOMEUSER is not a valid virtual path.
So how are you supposed to pass variables to the target page? what am I missing?
I think I solved this one myself.
Found this loop
foreach (KeyValuePair<string, object> token in requestContext.RouteData.Values)
{
requestContext.HttpContext.Items.Add(token.Key, token.Value);
}
from http://www.codethinked.com/post/2008/08/20/Exploring-SystemWebRouting.aspx
Its like the 4th code sample down.
UPDATE:
Not sure if this will work... requestContext.HttpContext seems to be "readonly". Back to the drawing board.
UPDATE 2:
Looks like this will work if you add in a reference to System.Web.Abstractions
Started mucking around with things and saw the IHttpHandler interface provides the RequestContext to the GetHttpHandler method.
So, I modified my base page class (I always put a layer between System.Web.UI.Page and my own pages, calling it BasePage or similar just for the purpose). So I added a public property on PVBasePage to receive a RequestContext object.
public RequestContext RequestContext { get; set; }
Then, my Routing class code is as follows:
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
{
// create the page object as my own page...
var page = BuildManager.CreateInstanceFromVirtualPath(VirtualPath
, typeof(PVBasePage)) as PVBasePage;
// pass in the request context
page.RequestContext = requestContext;
// return this page in the form of a IHttpHandler
return page as IHttpHandler;
}
So instead of, as in the sample code, creating the instance directly as the IHttpHandler, I create it as my own page. Set the request context property, and then return the page to the caller AS a IHttpHandler.
Tested and it works. WOO HOO!
Then in the instance page, you can hit the RequestContext.GetValues collection to read out your passed in parameters.
HTH
#B.Tyndall
I just got this working with a solution similar to yours.
found at: http://msmvps.com/blogs/luisabreu/archive/2008/03/12/using-the-routing-mvc-api-with-classic-asp-net.aspx
foreach (var aux in requestContext.RouteData.Values)
{
HttpContext.Current.Items[aux.Key] = aux.Value;
}
So in effect you're no longer using the Request.QueryString but instead Context.Items collection
HttpContext.Current.Items["RouteName"]
or
Context.Items["RouteName"]
It appears as though other are also taking the route (no pun intended) of putting the parameters in the context Items collection.
http://bbits.co.uk/blog/archive/2008/05/19/using-asp.net-routing-independent-of-mvc---passing-parameters-to.aspx
I combined a couple of these approaches for pages that have a specific parameter, I created a UserNameRouteHandler for pages that accept that type of parameter. In my PageBase class I checked the context items for that parameter and then set a property so that my pages that inherit from PageBase can use it.
public class UserNameRouteHandler : IRouteHandler
{
#region Implementation of IRouteHandler
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string pageName = requestContext.RouteData.GetRequiredString("PageName");
string employeeUserName = requestContext.RouteData.GetRequiredString("UserName");
if(!string.IsNullOrEmpty(employeeUserName))
{
requestContext.HttpContext.Items["UserName"] = employeeUserName;
}
pageName = pageName.ToLower() == "home" ? "default" : pageName;
string virtualPath = string.Format("~/{0}.aspx", pageName);
return (Page)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page));
}
#endregion
}
And in my OnLoad of PageBase I set the property to pages that need it can have it...definitely looking for a more elegant solution though.
protected override void OnLoad(EventArgs e)
{
if (!IsPostBack)
{
if (Context.Items["UserName"] != null)
{
EmployeeUserName = Context.Items["UserName"].ToString();
}
}
base.OnLoad(e);
}
I am trying to mock out HttpContext so that I can unit test my controller's Request.IsAuthenicated call. I am using the code that I found at Scott Hanselman's blog to simulate HttpContext using rhino.mocks.
so i have this unit test piece:
PostsController postsController = new PostsController(postDL);
mocks.SetFakeControllerContext(postsController);
Expect.Call(postsController.Request.IsAuthenticated).Return(true);
In my controller action, I have something like
if(Request.IsAuthenticated)....
when I try to run the unit test, the test fails throwing a null exception, and when I try to debug the unit test, I see that the HttpContext is never assigned to the controller.
any ideas?
This should work:
PostsController postsController = new PostsController(postDL);
var context = mocks.Stub<HttpContextBase>();
var request = mocks.Stub<HttpRequestBase>();
SetupResult.For(request.IsAuthenticated).Return(true);
SetupResult.For(context.Request).Return(request);
postsController.ControllerContext = new ControllerContext(context, new RouteData(), postsController);
This may be of some use to you, worked for me in a similar scenario:
http://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx
You may find the post I wrote on this to be helpful in some way
http://santoshbenjamin.wordpress.com/2008/08/04/mock-httpcontext-and-session-state/
cheers
benjy
Now, for disclosure, I have yet to get my hands dirty with most of the stuff you are working with, however:
If you want to mock the IsAuthenticated, why not just create a static class to return a bool that can the be manipulated by your test code?
This is a bit rough round the edges, but hopefully you get the idea:
interface IAuthenticationChecker
{
bool IsAuthenticated { get; }
}
public class MockAuthenticationChecker : IAuthenticationChecker
{
static bool _authenticated = false;
public static void SetAuthenticated(bool value)
{
_authenticated = value;
}
#region IAuthenticationChecker Members
public bool IsAuthenticated
{
get { return _authenticated; }
}
#endregion
}
public class RequestAuthenticationChecker : IAuthenticationChecker
{
#region IAuthenticationChecker Members
public bool IsAuthenticated
{
get {
if (HttpContext.Current == null)
throw new ApplicationException(
"Unable to Retrieve IsAuthenticated for Request becuse there is no current HttpContext.");
return HttpContext.Current.Request.IsAuthenticated;
}
}
#endregion
}
You can then use a reference to either at app level, yeah it means you have to add a reference at app level, and you need to use a different ref rather than Request, but you also get complete control over the authentication for testing :)
FYI - this is totally open to being blown apart, I threw it together in about a minute :)
Here is one simple way to fake the context, found it from Jeff's blog :
TextWriter tw = new StringWriter();
HttpWorkerRequest wr = new SimpleWorkerRequest("/webapp", "c:\\inetpub\\wwwroot\\webapp\\", "default.aspx", "", tw);
HttpContext.Current = new HttpContext(wr);
Here's a class that may be useful. It handles ajax requests, user authentication, request parameters and more: https://gist.github.com/3004119