ASP.NET MVC Pass Auth info to ViewModel from Controller Method Attribute - c#

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)

Related

.net core routing, understand what application called with a generic route

So what I have is a base controller that the following [Route] definition
[Route("{application}/api/[controller]")]
public class BaseController
{
}
All of my current controllers inherit from BaseController.
What I am trying to achieve is that two different application can call my controllers and my code to be aware of what 'application' is calling it.
Application 1 should be able to call /Application1/Api/MyController
Application 2 should be able to call /Application2/Api/MyController
and both requests should go to the same controller but my code should be aware of which application called it.
I thought about having some sort of Middleware and then work out the application from the Request.Path, and then store it in something like HttpContext.Current.Items but that doesn't seem like the correct way to do it.
My personal preference here would be to pass the value as an HTTP header rather than a route parameter, especially if you want it everywhere. It means you don't need a Route attribute and a different URL per application. Using a custom ActionFilterAttribute, there's a bunch of ways you can pass this detail into your action. For example:
public class ApplicationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Request.Headers.TryGetValue("Application", out var values))
{
// Method 1: This allows you to specify a parameter on your action
context.ActionArguments.Add("application", values.First());
// Method 2: This adds the value into the route data
context.RouteData.Values.Add("Application", values.First());
// Method 3: This will set a property on your controller
if (context.Controller is BaseApplicationController baseController)
{
baseController.Application = values.First();
}
}
base.OnActionExecuting(context);
}
}
And apply it to action methods or your controller:
[Application]
public class FooController : Controller
{
}
Method 1 Usage:
public IActionResult Index(string application)
{
// do something with the parameter passed in
}
Method 2 Usage:
public IActionResult Index(string application)
{
var application = (string)RouteData.Values["Application"];
}
Method 3 Usage:
First, create a base controller that contains the property:
public abstract class BaseApplicationController : Controller
{
public string Application { get; set; }
}
Then make sure your controller inherits from it:
[Application]
public class FooController : BaseApplicationController
{
}
Now you can access the property on your controller:
public IActionResult Index(string application)
{
var application = this.Application;
}
Bonus Method 4:
As an aside, you could use this method to use the URL route value, using the base controller from method 3, modify the attribute to look like this:
public class ApplicationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Controller is BaseApplicationController baseController)
{
baseController.Application = (string)context.RouteData.Values["application"];
}
base.OnActionExecuting(context);
}
}
Add a route attribute to your controller:
[Route("{application}/api/[controller]/[action]")]
And now you should have the property value on the controller set.
You could move the route template into action and then each action would technically be aware of its caller context by your proposed convention:
[Route("api/[controller]")]
public class YourController : BaseController
{
[HttpGet("{application}")]
public IActionResult Get(string application)
{
if (application == "Application1")
{
...Application1 called
}
if (application == "Application2")
{
...Application2 called
}
...
}
}
Of course, this is your proposed convention and it is not enforced through some custom application authentication in any way so you will have to trust that your callers will correctly identify themselves through this convention.
Another approach, could be to have a base class variable and set that after inspecting the route.
[Route("{application}/api/[controller]")
public class BaseController: Controller
{
protected string CallingApp { get; set; }
public override void OnActionExecuting(ActionExecutingContext ctx)
{
CallingApp = ctx.RouteData.Values["application"];
base.OnActionExecuting(ctx);
}
}

c# .net access parent obj

I am using .net c# MVC controller to query database for many of my projects. Every time i create a new controller, I find myself having to rewrite some of the same function for the new controller hence, I thought about writing a basic controller to handle some of the basic task that I use in all my controller (e.g., run a query and run json).
In my controller, I reference the basic controller like this.
namespace myWebAPI.Controllers
{
public class esrcController : Controller
{
//
// GET: /esrc/
string db = "esrc-";
basicController BasicController = new basicController();
public string test()
{
return "test" + Request.ServerVariables["HTTP_REFERER"];
}
public string getCodingException()
{
return #"{""data"":" + BasicController.getDataNconvertToJSON(
"select * from z_codingexception order by nc_key",
BasicController.getEnviroment(db)) + "}";
}
}
}
in my BasicController, the getEnviroment looks at the url to determine the environment hence I need access to :
Request.ServerVariables["HTTP_REFERER"] and Request.ServerVariables["HTTP_HOST"].ToString().ToLower();
but Request is null in this controller, I only have access to request in the main controller. How do I reference httpRequest from basic controller?
Just because you instantiate a new instance of a controller, doesn't mean you'll have access to the context.
One option is to create an abstract base controller that all of your other controlers would inherhit from. You'll then have access to the specific objects like Request
WebApiConfig.cs
config.MapHttpAttributeRoutes();
Your Controller
public abstract class MyBaseController : Controller
{
protected void myMethod()
{
// you have access to Request here
}
}
public class MyController : MyBaseController
{
[HttpGet]
[Route("my/getstuff")]
public IHttpActionResult GetStuff()
{
// do stuff
base.myMethod();
return Ok();
}
}
Create an action filter and add it as an attribute to that class. Within the action filter yuo wil have access to the Request object. If you override the OnActionExecuting function, the functionality in your filter will be executed before your controller.
Create a custom filter
public class CustomAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//DO STUFF WITH YOUR REQUEST OBJECT HERE..
}
}
Add the filter as an attribute to your controller
[CustomAttribute]
public class esrcController : Controller

Why does User.Identity.GetUserId<int>() in WebApi Controller return 0 everywhere except in action methods?

I'm working on a WebApi project with Asp.Net.Identity v2 (with AspNetUser modified to use int IDs). I wanted to do a bit of refactoring by creating a base class for all my web api controllers. In particular, I wanted the MyBaseController class to encapsulate the logic of getting the current user id.
Previously, each action method in each controller called User.Identity.GetUserId<int>(). it was cumbersome but worked.
Now I decided to encapsulate that call either in the base class's constructor or in its property. However, it doesn't work: The Identity object I get is empty (IsAuthenticated = false, Name = null...), and GetUserId<int>() always returns 0.
Apparently it is only inside an action method that Identity is populated and GetUserId<int>() returns a correct result?
Here is basically what I get:
public class MyBaseController : ApiController
{
public int UserId => User.Identity.GetUserId<int>(); // **this always returns 0**
public MyBaseController() : base()
{ var userId = User.Identity.GetUserId<int>(); // **this is always 0** }
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
var userId = User.Identity.GetUserId<int>(); // **also 0**
}
public IHttpActionResult Get() {
var userId = User.Identity.GetuserId<int>(); // **the only place where it returns userId properly**
// some code ...
}
}
Is there a way to grab User.Identity other than in an action method to do something with it in a base class?
The project was initially created in VS 2013 with MVC 5 / WEbapi 2, now migrated to VS2015
You cannot access User.Identity before authorization. See the APIController life cycle here or on MSDN.
The information is not yet available in the constructor or in Initialize().
You can use a property in the base controller:
public abstract class MyBaseController : ApiController
{
public int UserID
{
get { return User.Identity.GetUserId<int>(); }
}
}
public class MyNormalController : MyBaseController
{
public ActionResult Index()
{
var userId = UserID; // put in variable to avoid multiple calls
// some code ...
}
}

How to use asynchronous ViewModel in every controller action?

I'm new to asp.net mvc (5) and I am facing an issue on my website.
Basically, all aspnet_users are linked to my specific user table via the guid.
I have a BaseController class with a UnitOfWork and a ViewModelBase :
public class BaseController : Controller
{
protected UnitOfWork UnitOfWork { get; private set; }
public ViewModelBase ViewModel { get; set; }
}
(Extract of) the ViewModelBase class, containing the information needed in the layout page :
public class ViewModelBase
{
public User User { get; set; }
public bool IsAuthentified { get; set; }
public bool Loaded { get; set; }
}
And the layout which uses it :
#model Project.ViewModels.ViewModelBase
<html>
<body>
Some very cool layout stuff related to the User in the ViewModelBase
</body>
</html>
Here is an example of use with the HomeController :
public class HomeController : BaseController
{
private new HomeViewModel ViewModel
{
get { return (HomeViewModel)base.ViewModel; }
}
public HomeController()
{
ViewModel = new HomeViewModel();
}
public ActionResult Index()
{
// I need to get the MembershipUser here to retrieve the related User and set it in the ViewModel
return View(ViewModel);
}
}
The HomeViewModel :
public class HomeViewModel : ViewModelBase
{
public string HomeSpecificProperty { get; set; }
}
And the view :
#model Project.ViewModels.HomeViewModel
#{
ViewBag.Title = "Home";
Layout = "~/Views/Shared/Layout.cshtml";
}
Welcome #Model.User.UserName !
<br />
#Model.HomeSpecificProperty
And here is my problem. The thing is all the pages have a viewmodel inherited from ViewModelBase, since it is used in the layout, but I don't know where nor how to set the User property in it.
Since Membership is used to retrieve the User it has to be in an Action, I can't do this in the ViewModelBase constructor.
Thus I added this code in the BaseController, which sets the ViewModelBase properties on the first get :
private ViewModelBase viewModel;
protected ViewModelBase ViewModel
{
get
{
if (!viewModel.Loaded)
LoadBaseContext();
return viewModel;
}
set { viewModel = value; }
}
private void LoadBaseContext()
{
viewModel.IsAuthentified = HttpContext.User.Identity.IsAuthenticated;
if (viewModel.IsAuthentified)
{
MembershipUser user = Membership.GetUser();
viewModel.Player = UnitOfWork.UserRepo.Get((Guid)user.ProviderUserKey);
}
viewModel.Loaded = true;
}
Might not be very beautiful, but works. However, since there is some database acesses in it (notably to get the User), I thought I should put the LoadBaseContext function async.
I tried, and since all actions use ViewModelBase I put every action async too. But then #Html.ActionLink doesn't work anymore since the action called is async.
Finally, my question is : Is this ViewModelBase and User property the right way to do ? If it is, should the LoadBaseContext be async ? Then, how to make it work with the actions ?
Thanks a lot.
UPDATE
Extract of the layout :
#if (!Model.IsAuthentified)
{
#Html.Action("Index", "Authentification", null) // display login partial view
}
else
{
#Html.Action("Index", "UserInfos", null) // display userinfos partial view
}
Authentification controller Index's action :
public ActionResult Index()
{
ViewModel = new AuthentificationViewModel();
// loads the ViewModelBase properties here
return PartialView("~/Views/Shared/Partials/Login.cshtml", ViewModel);
}
If you have something that you always want in all of your views, you could either ensure that every view takes a view model that includes or extends that core set of data, or you could simply put the data in the ViewBag.
To do the latter, you could override OnActionExecuting in your base controller, and set the ViewBag contents there.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
this.ViewBag["IsAuthenticated"] = this.Request.IsAuthenticated;
// ...
}
If you want async behaviour, then you might be able to adapt this post to your needs.
To do this without overriding OnActionExecuting, you could put an async method in your controller base class that sets the data in the ViewBag:
protected async virtual Task PopulateViewBag()
{
this.ViewBag["foo"] = await MyAsyncMethod();
// ...
}
... and then simply call this from each and every one of your controller actions:
public async Task<ActionResult> MyAction()
{
await this.PopulateViewBag();
// ... more code
}
It would be a bit tedious, though.
One final word: depending on how many users you expect to have etc. it may be easier to get the user information once, and then cache it (e.g. in the Session), rather than repeatedly get it during every request. Presumably most of the user info isn't going to change between requests...

Abstraction layer between Controller and View

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.

Categories