I am trying to create another layer between my controller and my view so that I can pass different versions of a view to a user based on their "client ID" which would be the company to which they belong.
I have the following code:
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
// set client
var client = new Client();
client.Id = Guid.NewGuid();
client.Name = "Foo";
// set user
var user = new User();
user.Id = Guid.NewGuid();
user.ClientId = client.Id;
user.Name = "Foo";
return ViewRenderer.RenderView("AddComplete", client);
}
}
My ViewRenderer class looks like this:
public static class ViewRenderer
{
public static ViewResult RenderView(string view, Guid clientId)
{
string viewName = GetViewForClient(view, clientId);
return Controller.View(view);
}
public static string GetViewForClient(string view, Guid clientId)
{
// todo: logic to return view specific to the company to which a user belongs...
}
}
The problem is, the line return Controller.View(view); in RenderView(string view, Guid clientId) gives me the error:
System.Web.Mvc.Controller.View()' is inaccessible due to its
protection level
I am interested to know how I can resolve this error or if there is a better way to do what I am trying to do, which is to display different versions of a view which are specific to the respective company to which a user belongs.
Edit: Another option I was kicking around in my head...
Is there a way to override the View() method such that I can prepend it with a directory name, for example, a user who belongs to "Acme Co." would call the same controller action as everyone else like View("MyView") but the method would actually be calling View("AcmeCo/MyView") however, I don't actually write that code in my controller, it's just derived from the user's client ID property.
You can just replace the view engine instead of adding another abstraction.
Write your own View engine (here is how to start off with a RazorViewEngine)
public class ByIdRazorViewEngine : RazorViewEngine
{
protected override IView CreateView(ControllerContext controllerContext,
string viewPath, string masterPath)
{
var id = // get something from controller context controllerContext
var newViewPath = CalculateViewPathFromId(id);
return base.CreateView(controllerContext, newViewPath, masterPath);
}
And register it in Global.asax.cs:
protected void Application_Start()
{
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new ByIdRazorViewEngine());
}
The View() method is a protected member. You can only access it from within a derived type, such as your HomeController class. Plus you're trying to access it as a static method.
You can create a base Controller that exposes your specialized view logic. For the sake of illustration, I'm going to call it DynamicViewControllerBase
public class HomeController : DynamicViewControllerBase
{
//
// GET: /Home/
public ActionResult Index()
{
// set client
var client = new Client();
client.Id = Guid.NewGuid();
client.Name = "Foo";
// set user
var user = new User();
user.Id = Guid.NewGuid();
user.ClientId = client.Id;
user.Name = "Foo";
return RenderView("AddComplete", client);
}
}
public class DynamicViewControllerBase : Controller
{
protected ViewResult RenderView(string view, Guid clientId)
{
string viewName = GetViewForClient(view, clientId);
return View(view);
}
// Unless you plan to use methods and properties within
// the instance of `Controller`, you can leave this as
// a static method.
private static string GetViewForClient(string view, Guid clientId)
{
// todo: logic to return view...
}
}
If all you want to have is the company name prefixed to your controllers, apply the RoutePrefix attribute on to your controller.
Example:
[RoutePrefix(#"{company}")]
public partial class HomeController : Controller
{
}
And in your RouteConfig file,
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Make sure this line is added
routes.MapMvcAttributeRoutes();
}
Since your users must be authenticated to sign in to their accounts, once they've authenticated them selves you can either:
Store a cookie on your users machine with the name of their company
Make calls to your database on each request to retrieve this information
Make use of ViewData[]
etc..
Once you have the name of their company, you can construct the urls with that name.
Example:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model)
{
// ... authenticate user etc
// Redirect to
// foo.com/abc/home
return this.RedirectToAction("Index", "Home", new { company = "abc" });
}
If you're trying to work a way around this, I doubt you'll be able to as the web request first comes through a route, and the route decides which controller/action is executed, but to know the company name your action needs to execute to retrieve.
Related
I have a basic controller that extends from Controller, the class is working fine, but I figured that I am using a lot of times the code to get the current User from the database. So I figured I should make a constructor and move the code that I use in every function there.
Basically, what I wanted to do is have the parameters ready for any of the methods in my controller.
So, this is what I have right now (and it is working fine):
public class UsersController : Controller
{
private DBContext db = new DBContext();
public ActionResult Info()
{
User user = db.Users.Where(m => m.username.Equals(User.Identity.Name)).FirstOrDefault();
return View(user);
}
public ActionResult Edit(int? id){
User user = db.Users.Where(m => m.username.Equals(User.Identity.Name)).FirstOrDefault();
if(user.id == id){
return View(user);
}
}
}
But my idea was to create something like this:
public class UsersController : Controller
{
private DBContext db = new DBContext();
private User _user;
public UsersController()
{
_user = db.Users.Where(m => m.username.Equals(User.Identity.Name)).FirstOrDefault();
}
public ActionResult Info()
{
return View(_user);
}
public ActionResult Edit(int? id){
if(_user.id == id){
return View(_user);
}
}
}
When I made these changes I get the following error:
Server Error in '/' Application.
Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
I tried debugging and I found out that the problem is that my User is null when the constructor is called, so I am guessing, some other languages can call the parent constructor before adding or after adding their own customization, for example something like this:
public function __Construct($x){
$this->x = $x
parent::__construct();
}
or
public function __Construct($x){
parent::__construct();
$this->x = $x
}
I tried to do the same in my program, using base, but nothing seems to work and it always leads me to an error of some other nature.
I am not even sure that this is the right way to do it, because all I need is to have my User (Identity) created in the constructor
Sounds like the user isn't found, possibly because the user identity isn't populated on the thread's principal when the constructor for the controller is called.
My suggestion would be to avoid pulling the user data in the constructor and instead grab it when you need it. To avoid duplicating code, you can write a protected or private method (not an action method) to get it:
public class UsersController : Controller
{
private DBContext db = new DBContext();
private User GetCurrentUser()
{
return db.Users.Where(m => m.username.Equals(User.Identity.Name)).FirstOrDefault();
}
public ActionResult Info()
{
var user = GetCurrentUser();
return View(user);
}
public ActionResult Edit(int? id){
var user = GetCurrentUser();
if(user.id == id){
return View(user);
}
}
}
As I mentioned in my question comment, inheritance is a poor choice here. Instead what you're attempting to do is give non-specific data to a View. A better choice is to use an ActionFilter.
We need a class to store User Information for the view to consume:
public class UserInfo
{
public bool HasUser { get; set; }
public User User { get; set; }
}
We need a place to store the data that is non-specific to views. I prefer using ViewData (because this route provides strongly typed data and an easy way to debug this storage location):
public static class ViewDataExtensions
{
private const string UserInfoKey ="_UserInfo";
public static void GetUserInfo(this ViewData viewData)
{
return viewData.ContainsKey(UserInfoKey)
? viewData[UserInfoKey] as UserInfo
: null;
}
public static UserInfo SetUserInfo(this ViewData viewData, UserInfo userInfo)
{
viewData[UserInfoKey];
}
}
Next we need a way to populate that information when needed
public class AddUserToViewDataFilterAttribute : ActionFilterAttribute
{
private DBContext db = new DBContext();
public void OnActionExecuting(ActionExecutingContext context)
{
var user = context.Controller.User;
var userInfo = new UserInfo
{
HasUser = !string.IsNullOrEmpty(User.Identity?.Name),
User = !string.IsNullOrEmpty(User.Identity?.Name)
? db.Users
.Where(m => m.username.Equals(User.Identity.Name)).FirstOrDefault()
: null;
};
context.ControllerContext.ViewData.SetUserInfo(userInfo);
}
}
Populate it when needed:
public class MyController
{
public ActionResult DoesNotNeedUserInfo()
{
}
[AddUserToViewDataFilter]
public ActionResult NeedsUserInfo()
{
}
}
In the view:
#model <whatever>
#if (ViewData.GetUserInfo().HasUser) {
<div>#ViewData.GetUserInfo().User.Name</div>
}
I want for the Attributes associated to a controller action to be called during the test.
E.g. Code
[TestMethod]
public void UserRegistersWithNonMatchingPasswords()
{
var model = GetModel();
var controller= GetController();
controller.Register(model); //This is an ActionResult method
AccountRepository
.DidNotReceive()
.IsAccountExists(Arg.Any<string>());
AccountRepository
.DidNotReceive()
.CreateUser(Arg.Any<string>(), Arg.Any<string>());
}
Now the register method looks like
[HttpPost]
[ValidateModel]
public ActionResult Register(Model model)
{
//The validate model attribute checks the ModelState.IsValid and returns if it's not valid, so any code in the method shouldn't run because of the failed validation.
IsAccountExists();
CreateUser();
}
The ValidateModel attribute code (It's from Sitecore Habitat framework)
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var viewData = filterContext.Controller.ViewData;
if (!viewData.ModelState.IsValid)
{
filterContext.Result = new ViewResult
{
ViewData = viewData,
TempData = filterContext.Controller.TempData
};
}
}
}
However, it's the MVC framework that calls these attributes, so I wanted to know how to make NSubsitute call it, because it ignores attributes. Thanks.
I am reworking one asp.net MVC Backoffice for full multi tenant support.
As I decided to use sharding for a perfect separation between tenants I need to access some Auth info (tenant Id) in every viewModel, only with that info I can create the right connection for the specified tenant.
For passing that info I have 3 rules:
I don't want to use session variables
I don't want to use ViewBag
I don't want to add a new Object to every VM for get auth info
Most of all I want a "code less" solution, the perfect scenario would be getting access of auth info inside the VM for example passing it as a attribute in the controller call.
I already override Controller OnAuthorization so that it adds the tenantId to my base controller (which is the base of all my controllers) every time it is called, that way I can always catch tenantId inside every controller, now I just need a way to pass that TenantId in a attribute to every VM , something like the following pseudo-code
[Authorize]
[TenantId AS A PARAM]
public ActionResult Index()
{
myViewModel vm = new myViewModel();
vm.method();
return this.View(vm);
}
1) Put this TenantId into HttpContext.Curent.Items
2) Write a static function that returns this TenantId from the context:
private static int GetTenantId()
{
return HttpContext.Current.Items["TenantId"];
}
3) Create a BaseViewModel
public abstract class BaseViewModel
{
public Func<int> GetTenantIdFunc{get;set;}
}
4) Using Dependency Injection container register your GetTenantId function and inject it through property injection to all your models
One option is to use a base view model and inheritance:
public abstract BaseViewModel()
{
public int TenantId { get; set; }
public void SetAuthInfo(BaseController controller)
{
this.TenantId = controller.TenantId;
}
}
public MyViewModel() : BaseViewModel
// no other changes needed to MyViewModel
...
public ActionResult Index()
{
var model = new MyViewModel();
model.SetAuthInfo(this);
return View(model);
}
To get this via an attribute (rather than model.SetAuthInfo) add an action filter and override OnResultExecuted and add it there, something like (untested) :
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class SetTenantActionAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var baseController = filterContext.Controller as BaseController;
if (baseController == null) return;
var model = filterContext.Controller.ViewData.Model as BaseViewModel;
if (model == null) return;
model.TenantId = baseController.TenantId;
}
}
then you could add this to your base controller (even less code than adding to every action)
I have implemented my own custom Authorize attribute.
The attribute is applied both at the controller level and at the action level.
Here is an example of what I need to do:
[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
[ClaimsAuthorize(Roles = "Administrators")]
public ActionResult AdministrativeTask()
{
return View();
}
public ActionResult SomeOtherAction()
{
return View();
}
}
Currently if a user has the Administrator Role but not the AdvancedUsers role, he cannot execute "Administrative Task".
How can I change this behavior to perform a security check at the action level even if the user is not authorized at the controller level?
For the moment, the only solution I can think about is to implement 2 attributes: one for securing controllers, another for securing actions. Then I would play with the Order property to execute the one at the action level first.
However, I would prefer a solution with a single attribute if possible.
Use built-in [OverrideAuthorization]:
[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
[OverrideAuthorization]
[ClaimsAuthorize(Roles = "Administrators")]
public ActionResult AdministrativeTask()
{
return View();
}
public ActionResult SomeOtherAction()
{
return View();
}
}
OverrideAuthorization Attribute is available for MVC 5 (at least) and up. Once you decorate the Action with it, also decorate with the new Role and that will take effect over the Controller level Role.
This should not be possible. Imagine the logic which MVC uses with the authorization filters.
When the controller is determined - check if there is an authorization filter that applies to that controller and execute it.
When the action is known - do the same for the action.
In all cases a fail in authorization would short-circuit the pipeline.
To make specific actions restricted you simply use the Authorize-attribute on the methods that handle these actions.
When you mark an action method with the Authorize attribute, access to that action method is restricted to users who are both authenticated and authorized.
//[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
{
[ClaimsAuthorize(Roles ="Administrators", "Role2","Role3")]
public ActionResult AdministrativeTask()
{
return View();
}
}
OR you can override your authorization at controller level ,
Create a new OverrideAuthorizeAttribute attribute.
public class OverrideAuthorizeAttribute : AuthorizeAttribute {
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
}
and you can use this attribute to override your controller level autorization.
[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
[ClaimsAuthorize(Roles = "Administrators")]
public ActionResult AdministrativeTask()
{
return View();
}
[OverrideAuthorizeAttribute(Roles ="xxxx")] // This role will override controller
//level authorization
public ActionResult SomeOtherAction()
{
return View();
}
}
You need two authorization attributes - a base one with all authorization logic, and a second one, derived from the base attribute, that is only used to override the base attribute.
Example authorization attributes:
public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
protected bool _canOverride = true;
//...custom authorization code goes here.....
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
//Don't authorize if the override attribute exists
if (_canOverride && actionContext.ActionDescriptor.GetCustomAttributes<OverrideClaimsAuthorizeAttribute>().Any())
{
return;
}
base.OnAuthorization(actionContext);
}
}
public class OverrideClaimsAuthorizeAttribute : ClaimsAuthorizeAttribute
{
public OverrideClaimsAuthorizeAttribute ()
: base()
{
_canOverride = false;
}
}
In the base authorization attribute we are saying to go ahead and authorize as normal, as long as the OverrideClaimsAuthorizeAttribute doesn't exist. If the OverrideClaimsAuthorizeAttribute does exist, then only run the authorization on classes where _canOverride is false (ie the OverrideClaimsAuthorizeAttribute class itself).
Example usage:
[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
//Ignores the controller authorization and authorizes with Roles=Administrators
[OverrideClaimsAuthorize(Roles = "Administrators")]
public ActionResult AdministrativeTask()
{
return View();
}
//Runs both the controller and action authorization, so authorizes with Roles=Administrators AND Roles=AdvancedUsers
[ClaimsAuthorize(Roles = "Administrators")]
public ActionResult AdvancedAdministrativeTask()
{
return View();
}
//authorizes with controller authorization: Roles=AdvancedUsers
public ActionResult SomeOtherAction()
{
return View();
}
}
Check this previous question. (check #AndyBrown answer, case 2)
For a simple way you might also try adding (
[AllowAnonymous]) to override the controller
[Authorize]
then add a new custom filter to check for your logic for this particular action. Or you can add the code that checks for the role just inside it.
I'm creating a website in C# using MVC3. The website is a client that uses a web service. The web service uses a unique session id per user, which is given as the last parameter in each service call. You get the session id when you log in.
I'm able to get the session id and save it in a property in my user controller:
private string Sid
{
get
{
var sid = Session["Sid"];
return sid == null ? "" : sid.ToString();
}
set
{
Session["Sid"] = value;
}
}
In my other controllers I'm able to get the session id with a similar property (just without the setter), but when it asks the user controller to do something where it accesses its own property to get the id the session is null.
It seems like the sessions don't get transferred between the controllers, but I don't know how to do that. I would like to access the session from one central place, instead of having properties in each controller, but I can't figure out how to do it.
After three days of searching Google hasn't been able to give me the answer. Do you have any ideas?
Why not create a Base Controller which creates the Session variable in the OnActionExecuting method, then make all your other Controllers inherit from this Base Controller, like so:
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
Session["Sid"] = Session.SessionID;
base.OnActionExecuting(filterContext);
}
}
public class HomeController : BaseController
{
public ActionResult Index()
{
ViewBag.Name = Session["Sid"];
return View();
}
}
public class TestController : BaseController
{
public ActionResult Index()
{
ViewBag.Name = Session["Sid"];
return View();
}
}
Hope this helps!
Edit
As per your comment below here is, what I believe, you're looking for (?):
public class BaseController : Controller
{
public string Sid
{
get
{
var sid = Session["Sid"];
return sid == null ? "" : sid.ToString();
}
set
{
Session["Sid"] = value;
}
}
}
public class HomeController : BaseController
{
public ActionResult Index()
{
Sid = "2343432233aaaa";
ViewBag.Name = Session["Sid"];
return View();
}
}
public class TestController : BaseController
{
public ActionResult Index()
{
ViewBag.Name = Session["Sid"];
return View();
}
}
You can replace where I set Sid with the SessionId generated from your service
The behaviour you are describing is very unexpected; the Session is definitely the same, so there is something else causing this behaviour.
However, the "usual" way to deal with this is to create a custom SessionWrapper. The SessionWrapper is a static class with public properties, and most importantly, it's the single point where you will access the session - so all the controllers will use this class for writing to/reading from the session.
A very simple version would look something like this:
public static class SessionWrapper
{
public string Sid
{
get
{
var sid = Session["Sid"];
return sid == null ? "" : sid.ToString();
}
set
{
Session["Sid"] = value;
}
}
}