Given the following masterpage or content page ...............
namespace Intranet
{
public partial class Site : System.Web.UI.MasterPage
{
WebSite.Security.Users.CurrentUser currentUser;
protected void Page_Init(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
currentUser = new WebSite.Security.Users.CurrentUser();
currentUser = WebSite.Security.Users.GetCurrentUser(currentUser);
Label_UserId.Text = currentUser.UserId;
}
}
}
}
that calls the following ............
namespace Intranet
{
public class WebSite
{
public class Security
{
public class Users
{
public class CurrentUser
{
public string UserId { get; set; }
}
public static CurrentUser GetCurrentUser(CurrentUser cu)
{
cu.UserId = "MethodThatGetsUserId";
return cu;
}
}
}
}
}
Will the returned instantiated class 'currentUser' contain unique information even if several different users are on the page at the same time?
Thanks for your time and insight.
Yes, a new class is instantiated for each request even, not just each user.
Static fields in the class will be shared, and you should use session and application data to share data across requests or users.
Nope, with this line:
currentUser = new WebSite.Security.Users.CurrentUser();
You are creating a new instance in your master page class. Instances created in each request are only available in that request (of course, depending on the scope), unless you use static variables. Static variables are the same for all users/threads in your application.
However, what you actually want to do is to get the current user. This should be done using the HttpContext.Current.User or Page.Current which is an IPrincipal and should contain information you filled in the Authenticate_Request method of your application.
To understand more about the ASP.NET forms authentication, please refer to: http://msdn.microsoft.com/en-us/library/9wff0kyh(v=vs.100).aspx
Related
So just some background on how the current UI automation solution works -
Our application is a Windows WPF app, so we utilize WinAppDriver for our automated testing needs. The solution for this is very similar to your typical UI automation page object design. We have page objects that reference elements, and then in our tests we call the methods from these page objects to perform actions on the host. The page objects make use of the C# partial classes. One class to store elements, one class to use these elements and perform actions
The test classes all inherit from a TestClassBase that handles the StartUp and TearDown login. So current design for something like a Login page and a test class that interacts with it looks like this
Login.Elements.cs
namespace UITesting
{
public partial class Login
{
public WindowsElement usernameField => _session.FindElementByAccessibilityId("UserName");
public WindowsElement passwordField => _session.FindElementByAccessibilityId("Password");
public WindowsElement signInButton => _session.FindElementByAccessibilityId("Sign In");
}
}
Login.Actions.cs
namespace UITesting
{
public partial class Login
{
// Driver Setup
private readonly WindowsDriver<WindowsElement> _session;
public Login(WindowsDriver<WindowsElement> session) => _session = session;
// Enter Username
public void EnterUsername(string username)
{
usernameField.SendKeys(username);
}
// Enter Password
public void EnterPassword(string password)
{
passwordField.SendKeys(password)
}
// Click 'Sign In'
public void SignIn()
{
signInButton.Click();
}
}
}
LoginTests.cs
namespace UITesting.Test
{
[Category("Login Tests")]
class LoginTests : TestClassBase
{
[Test]
public void Login()
{
// Login
login.EnterUsername("TestUser1");
login.EnterPassword("Password");
login.ClickSignIn();
}
}
}
TestClassBase
namespace UITesting
{
[TestFixture]
public class TestClassBase
{
// Declare Page Ogjects
public Login login;
// Declare WinAppDriver Session
private WindowsDriver<WindowsElement> session;
[SetUp]
public void SetUp()
{
// Instantiate Page Objects
login = new Login(session);
// Additional SetUp Logic here...
}
[TearDown]
public void TearDown()
{
// TearDown Logic here...
}
}
}
This all works well and great, but what I am trying to do is evolve this into is something that can run the exact same test using the same code on a different host.
We also have a Web version of the app that utilizes the Uno platform. The app is pretty much identical on the web, but to automate it we need to use Selenium. What I don't want to do is to have to manage two separate UI automation solutions, and since the two versions of the app are pretty much identical, I want to be able to toggle the target platform that the tests run on in our CI/CD pipeline and this will ultimately change what code is getting executed.
So it seems like utilizing Interfaces is probably the way to go here, and I understand that using them it would be possible to now have a Page Object class structure like below
ILogin.cs
LoginWeb.Actions.cs
LoginWeb.Elements.cs
LoginWPF.Actions.cs
LoginWPF.Elements.cs
This way, I now have 4 partial classes where the Actions classes inherit the interface and they use the elements from their corresponding Elements class.
The part that I don't understand is how I can get the test class to now execute the code from the desired Actions class. The part where I instantiate the page objects is key, as in this example both the WPF and Web page object would need to share the name login. Would I have to create two different TestClassBase classes and some sort of Interface for them and have the tests inherit both? Or am I just going about this the completely wrong way..
This might be a larger refactoring job, but it will be worth the effort.
First, you'll need to create interfaces for each page model. I recommend keeping the interfaces as simple as possible in order to provide a complete and flexible abstraction. Instead of three separate methods (EnterUsername, EnterPassword and ClickSignIn) which must be called in a specific order, consider a single method called SignIn which accepts a username and password as arguments. The method will internally handle entering the username, password and clicking the appropriate button.
Really, if you go this route, think hard about the interfaces. Try to avoid any situation where the order methods are called matters. Try to focus on the use case, and not the steps required to satisfy that use case.
public interface ILoginPage
{
void SignIn(string username, string password);
}
Next, implement this interface on two different classes. Each class will specialize in Selenium or WinAppDriver. Consider using a naming convention where page models that deal with the web application are prefixed with "Web" and page models for the desktop app are prefixed with "Windows" or "Desktop".
public class WebLoginPage : ILoginPage
{
private readonly IWebDriver driver;
public WebLoginPage(IWebDriver driver)
{
this.driver = driver;
}
public void SignIn(string username, string password)
{
// Enter username
// Enter password
// Click sign-in button
}
}
public class DesktopLoginPage : ILoginPage
{
private readonly WindowsDriver<WindowsElement> session;
public DesktopLoginPage (WindowsDriver<WindowsElement> session)
{
this.session = session;
}
public void SignIn(string username, string password)
{
// Enter username
// Enter password
// Click sign-in button
}
}
Once you have a proper abstraction, you will need an interface for a factory class that creates page models, and then two implementing classes:
public interface IPageModelFactory
{
ILoginPage CreateLoginPage();
}
public class WebPageModelFactory : IPageModelFactory
{
private readonly IWebDriver driver;
public PageModelFactory(IWebDriver driver)
{
this.driver = driver;
}
public ILoginPage CreateLoginPage()
{
return new WebLoginPage(driver);
}
}
public class DesktopPageModelFactory : IPageModelFactory
{
private readonly WindowsDriver<WindowsElement> session;
public DesktopPageModelFactory(WindowsDriver<WindowsElement> session)
{
this.session = session;
}
public ILoginPage CreateLoginPage()
{
return new DesktopLoginPage(session);
}
}
This is an implementation of the Abstract Factory Pattern, and is an approach you can take without resorting to class reflection. While class reflection would probably take less code, it is much more difficult to understand. Just for giggles, here is an attempt at class reflection to generate page models:
public class PageModelFactory
{
private readonly object client;
public PageModelFactory(object client)
{
this.client = client;
}
public ILoginPage CreateLoginPage()
{
var pageModelType = GetPageModelType<ILoginPage>();
var constructor = pageModelType.GetConstructor(new Type[] { client.GetType() });
return (ILoginPage)constructor.Invoke(new object[] { client });
}
private Type GetPageModelType<TPageModelInterface>()
{
return client.GetType()
.Assembly
.GetTypes()
.Single(type => type.IsClass && typeof(TPageModelInterface).IsAssignableFrom(type));
}
}
You can use it with either driver:
// Selenium
var driver = new ChromeDriver();
// WinApDriver (however you initialize it)
var session = new WindowsDriver<WindowsElement>();
PageModelFactory webPages = new PageModelFactory(driver);
PageModelFactory desktopPages = new PageModelFactory(session);
ILoginPage loginPage = null;
loginPage = webPages .CreateLoginPage();
loginPage.SignIn("user", "...");
loginPage = desktopPages.CreateLoginPage();
loginPage.SignIn("user", "...");
Unless you or your team are comfortable with class reflection, I would recommend the abstract factory pattern approach, just because it is easier to understand.
Either way, you will need to determine which client you are using (web versus desktop). This should be done in a the setup method for your test. Refactoring your tests into a base class to centralize this decision making code is advised.
I have a Singleton model class in my MVC application to determine if the user logging in has authorization/admin (based on memberships to certain AD groups). This model class needs to be a Singleton so that the user's access rights can be established once at first logon and used throughout the session:
public sealed class ApplicationUser
{
// SINGLETON IMPLEMENTATION
// from http://csharpindepth.com/articles/general/singleton.aspx#lazy
public static ApplicationUser CurrentUser { get { return lazy.Value; } }
private static readonly Lazy<ApplicationUser> lazy =
new Lazy<ApplicationUser>(() => new ApplicationUser());
private ApplicationUser()
{
GetUserDetails(); // determine if user is authorized/admin
}
// Public members
public string Name { get { return name; } }
public bool IsAuthorized { get { return isAuthorized; } }
public bool IsAdmin { get { return isAdmin; } }
// Private members
// more code
}
The Singleton is instantiated for the first time in my EntryPointController that all other controllers derive from:
public abstract class EntryPointController : Controller
{
// this is where the ApplicationUser class in instantiated for the first time
protected ApplicationUser currentUser = ApplicationUser.CurrentUser;
// more code
// all other controllers derive from this
}
This patterns allows me to use ApplicationUser.CurrentUser.Name or ApplicationUser.CurrentUser.IsAuthorized etc all over my application.
However, the problem is this:
The Singleton holds the reference of the very first user that logs in at the launch of the web application! All subsequent users who log in see the name of the earliest logged-in user!
How can I make the Singleton session specific?
I think you are looking for the Multiton pattern, where each instance is linked to a key.
An example from here
http://designpatternsindotnet.blogspot.ie/2012/07/multiton.html
using System.Collections.Generic;
using System.Linq;
namespace DesignPatterns
{
public class Multiton
{
//read-only dictionary to track multitons
private static IDictionary<int, Multiton> _Tracker = new Dictionary<int, Multiton> { };
private Multiton()
{
}
public static Multiton GetInstance(int key)
{
//value to return
Multiton item = null;
//lock collection to prevent changes during operation
lock (_Tracker)
{
//if value not found, create and add
if(!_Tracker.TryGetValue(key, out item))
{
item = new Multiton();
//calculate next key
int newIdent = _Tracker.Keys.Max() + 1;
//add item
_Tracker.Add(newIdent, item);
}
}
return item;
}
}
}
I got it working with a mixed Singleton-Multiton approach (thanks #Kickaha for the Multiton pointer).
public sealed class ApplicationUser
{
// SINGLETON-LIKE REFERENCE TO CURRENT USER ONLY
public static ApplicationUser CurrentUser
{
get
{
return GetUser(HttpContext.Current.User.Identity.Name);
}
}
// MULTITON IMPLEMENTATION (based on http://stackoverflow.com/a/32238734/979621)
private static Dictionary<string, ApplicationUser> applicationUsers
= new Dictionary<string, ApplicationUser>();
private static ApplicationUser GetUser(string username)
{
ApplicationUser user = null;
//lock collection to prevent changes during operation
lock (applicationUsers)
{
// find existing value, or create a new one and add
if (!applicationUsers.TryGetValue(username, out user))
{
user = new ApplicationUser();
applicationUsers.Add(username, user);
}
}
return user;
}
private ApplicationUser()
{
GetUserDetails(); // determine current user's AD groups and access level
}
// REST OF THE CLASS CODE
public string Name { get { return name; } }
public bool IsAuthorized { get { return isAuthorized; } }
public bool IsAdmin { get { return isAdmin; } }
private string name = HttpContext.Current.User.Identity.Name;
private bool isAuthorized = false;
private bool isAdmin = false;
// Get User details
private void GetUserDetails()
{
// Check user's AD groups and determine isAuthorized and isAdmin
}
}
No changes to my model and controllers.
The current user's object is instantiated in the EntryPointController:
public abstract class EntryPointController : Controller
{
// this is where the ApplicationUser class in instantiated for the first time
protected ApplicationUser currentUser = ApplicationUser.CurrentUser;
// more code
// all other controllers derive from this
}
In my model and everywhere else, I can access the current user's properties using ApplicationUser.CurrentUser.Name or ApplicationUser.CurrentUser.IsAuthorized etc.
How can I make the Singleton session specific?
Will lead to your problem below.
The Singleton holds the reference of the very first user that logs in
at the launch of the web application! All subsequent users who log in
see the name of the earliest logged-in user!
I think you just simply need to store your ApplicationUser object in session per user.
The mechanism should look like this:
Create an instance of your ApplicationUser every authenticated user.
Store ApplicationUser instance in a session with key. ( Don't worry about same key per user because ASP.NET HttpSessionState will handle it for you. )
If you want to access your ApplicationUser object per user just simply get it from HttpSessionState.
You have an option to create/re-create your session in Session_OnStart or in your base controller.
Setup your session setting if you want it to expire or not.
I hope this solution will make sense to you. :)
I’ve got an ASP.net MVC (5.2) site that runs using several subdomains, where the name of the subdomain is the name of a client in my database. Basically what I want to do is use the subdomain as a variable within my action methods to allow me to get the correct data from my database.
I did something similar a few years back, but it’s messy and not intuitive, so was wondering if there’s a better way to do it than I was using before. Here’s what I did before:
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
Session["subdomain"] = GetSubDomain(Request.Url);
}
private static string GetSubDomain(Uri url) {
string host = url.Host;
if (host.Split('.').Length > 1) {
int index = host.IndexOf(".");
string subdomain = host.Substring(0, index);
if (subdomain != "www") {
return subdomain;
}
}
return null;
}
Which basically assigned a key to the session variable if the subdomain was anything other than "www", but I’m really not happy with this way of doing it as it relies on me knowing that the session might contain this magic value!
Ideally I’d like to be able to create an attribute that I can decorate my classes/methods with that would extract the subdomain and then allow me to include a "subdomain" parameter in my action method that would contain the value extracted by the attribute. Is that even possible?
If that can’t be done, is there a better way of doing what I’m doing now without having to rely on the session?
Thanks,
Dylan
Your right this doesn't need to be stored in Session and IMHO shouldn't be, I would refactor this out into its own class and use HttpContext.Current.
public interface ISubDomainProvider
{
string SubDomain { get; set; }
}
public class SubDomainProvider : ISubDomainProvider
{
public SubDomainProvider()
{
string host = HttpContext.Current.Request.Url.Host; // not checked (off the top of my head
if (host.Split('.').Length > 1)
{
int index = host.IndexOf(".");
string subdomain = host.Substring(0, index);
if (subdomain != "www")
{
SubDomain = subdomain;
}
}
}
public string SubDomain { get; set; }
}
You choose how to use it, if your using an IoC container it would just be a case of injecting this class into your controller via the constructor, I like this because it is easier to Mock and Unit Test. Of course you can still do this:
public class SomeController : Controller
{
private readonly ISubDomainProvider _subDomainProvider;
public SomeController()
{
_subDomainProvider = new SubDomainProvider();
}
}
You could even create you own abstract Controller Class:
public abstract class MyAbstractController : Controller
{
public MyAbstractController()
{
SubDomain = new SubDomainProvider();
}
protected string SubDomain {get; set; }
}
public class SomeController : MyAbstractController
{
public ActionResult SomeAction()
{
// access the subdomain by calling the base base.SubDomain
}
}
You could set the name in the Session on the Session_Start event in the global.asax, this means it would only happen one time and would persist for the duration of the users' session
public void Session_Start(object sender, EventArgs e)
{
Session["subdomain"] = GetSubDomain(Request.Url);
}
Looks like there’s a good way of doing what I’m after at:
ASP.NET MVC Pass object from Custom Action Filter to Action
It essentially uses the route data to pass a custom parameter to the action, and can also pass objects other than simple strings etc.
On the plus side it avoids using the session and relying on magic values, but on the downside it means processing the URL for every request, which probably isn’t a good idea if a database is involved.
I'm creating a practice admin application using MVC4, but I'm not sure the best method to persist the logged in user data for the entire lifetime of the session so that it will be accessible to all views & controllers.
For example, I desire a user to log in, then download the user data from the database, and for the entire session I want to maintain the User model (Name, Database ID etc) so that it's accessible throughout the entire web application until the user is logged out.
Is the best approach to store this data in an encrypted cookie? Or is there a way of using a Static Class?
Currently I've read about using ViewModel Base class like so:
public abstract class ViewModelBase
{
public UserModel User { get; set; }
}
Then all of my ViewModels can inherit the base class, thus providing access to the user data model:
public class AllEmployeesViewModel : ViewModelBase
{
public List<EmployeeModel> Employees { get; set; }
}
However, if in one Controller action I set the user data, it will only be lost when loading another Controller Action.
To me it seems a waste of resources & will increase load times to have to keep downloading the user data from the database in every action.
All advice is much welcome for this new web programmer. If I've missed any important details, please do request it and I will try my best to answer.
You should look into SessionState to store data for the duration of the user's browser session.
http://msdn.microsoft.com/en-us/library/vstudio/ms178581(v=vs.100).aspx
I would abstract the Session for you application in a class in a way it is accessible to your controllers. Maybe an web application core class.
namespace MyApplication.Core
{
public class MySession
{
private const string _SessionName = "__MY_SESSION__";
private MySession() { }
public static MySession Current
{
get
{
MySession session =
(MySession)HttpContext.Current.Session[_SessionName];
if (session == null)
{
session = new MySession();
HttpContext.Current.Session[_SessionName] = session;
}
return session;
}
}
public UserModel CurrentUser { get; set; }
}
}
Somewhere in your login controller logic path
public void SomeLoginFunction(string userName,string password)
{
//DO AUTHENTICATION STUFF
MySession.Current.CurrentUser=aCurrentUserFromDB;
}
In your base class
public class ViewModelBase
{
public UserModel User { get{return {MySession.Current.CurrentUser;} }
}
I have a class declaration, public abstract class CompanyHttpApplication : HttpApplication
In the CompanyHttpApplication I have
public static CompanyHttpApplication Current
{
get { return (CompanyHttpApplication)HttpContext.Current.ApplicationInstance; }
}
and
public CompanyProfileInfo Profile
{
get
{
return profile ?? (profile = ProfileManager
.FindProfilesByUserName(ProfileAuthenticationOption.Authenticated,
User.Identity.Name).Cast<CompanyProfileInfo>().ToList().First());
}
private set { profile = value; }
}
Whenever I access the user:
CatapultHttpApplication.Current.Profile.UserName
it gets whoever is logged in last. I understand why (because it's in the application instead of the session), but I don't know how to change it. What can I do to change it so it uses the session instead of the application?