ASP.NET Access controller properties from AuthorizeAttribute method - c#

I have created a base controller for an API using an MVC 4 project. Everything works as I want, but in the interests of efficiency, I want to be able to access some of the custom properties from my base controller from the OnAuthorization method. I need to perform some SQL queries to make sure the access token is valid and so on. Id rather make this query once and store the object as a property on the controller so that i can access it later without needing to do a query again.
In short, this is what i want to do.
[APIActionFilter]
public class APIBaseController : ApiController
{
public APIClient client;
public class APIActionFilter : System.Web.Http.AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext filterContext)
{
//get data from the database.
Controller.client = objectmadefromdb;
}
}
}
There must be a reference to this object passed somewhere?

The first comment was along the lines, but was not correct. I was able to get this working using the following
public override void OnAuthorization(HttpActionContext filterContext)
{
var controllerRef = filterContext.ControllerContext.Controller as APIBaseController;
controllerRef.userdata = new user("123");
}
I was now able to access the properties from the main controller. I was able to set some public properties on the APIBaseController object and directly assign values to them. Could of course use getters and setters or whatever.
To confirm it works i was able to create a new controller that inherited the base controller. From any action within that controller I was able to access the properties of the APIBaseController object and they were populated with the data i set in the OnAuthorization method.

Related

ViewData in _LoginPartial

I have created ASP.NET Core application and now I'm trying to use ViewBag in _LoginPartial. I have created base controller:
public class BaseController : Controller
{
public ApplicationDbContext _db;
public BaseController(ApplicationDbContext db)
{
_db = db;
ViewData["MyKey"] = _db.MyTable.ToList();
}
}
All my controllers derive from this BaseController.
But when I see ViewData in _LoginPartial.cshtml, I can only see that it contains default Key=Title, Value=Home Page. What should I do to have MyKey available in _LoginPartial?
Thanks!
The problem is that you are trying to set ViewData content within the controller’s constructor.
The ViewData dictionary is not actually created by the controller. It is created at a very different point within the MVC pipeline and then gets injected into the controller. You can basically see this process as something like this:
// create controller
var controller = CreateController<MyController>();
// do stuff
// inject ViewData
controller.ViewData = GetViewDataDictionary();
// invoke controller action
var result = controller.SomeAction();
So the ViewData gets provided after the controller has been created; after its constructor ran. So any assignments to the view data within the constructor will not apply to the actual view data dictionary.
As such, you will need to set those values at a different time, you cannot use the constructor there. Using the ViewData in general is somewhat legacy construct that you should try to avoid if possible. Instead, you should work with strongly typed view model objects. Of course, this will require you to pass the data explicitly in each action, but that way you are also not introducing any implicit data flow.
An alternative, which is especially useful if what you are doing should actually always apply to the _LoginPartial, would be to use a view component. View components are reusable components that you can use inside of your views, which will behave similarly to a controller action. So you could just insert a view component into your partial, and have that run the logic (asynchronously even!) to provide the data from your database. And you wouldn’t need to mess with any of your controllers to make it work.
ViewData can be accessed after controller has been activated or by overrinding OnActionExecuting.
//
// Summary:
// Gets or sets Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary used by
// Microsoft.AspNetCore.Mvc.ViewResult and Microsoft.AspNetCore.Mvc.Controller.ViewBag.
//
// Remarks:
// By default, this property is intiailized when Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator
// activates controllers.
// This property can be accessed after the controller has been activated, for example,
// in a controller action or by overriding Microsoft.AspNetCore.Mvc.Controller.OnActionExecuting(Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext).
// This property can be also accessed from within a unit test where it is initialized
// with Microsoft.AspNetCore.Mvc.ModelBinding.EmptyModelMetadataProvider.
[ViewDataDictionary]
public ViewDataDictionary ViewData { get; set; }
For a solution, you could try overring OnActionExecuting like below:
public class BaseController : Controller
{
public ApplicationDbContext _db;
public BaseController(ApplicationDbContext db)
{
_db = db;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
ViewData["MyKey"] = _db.Product.ToList();
base.OnActionExecuting(context);
}
}
_LoginPartial.cshtml
#foreach (Product item in #ViewData["MyKey"] as IList<Product>)
{
<li><a>#item.Name</a></li>
}

How do you pass thread safe data between the methods of a System.Web.Http.Filters.ActionFilterAttribute?

I have a custom attribute extension of System.Web.Http.Filters.ActionFilterAttribute that I am using for logging with Web API controllers. I am experiencing an issue that indicates that the attribute object is being reused call to call. Data in my public properties from an initial call will appear in the logged information for a subsequent call and so on.
I read in this post that I "should never store instance state in an action filter that will be reused between the different methods." He goes on to say,"This basically means that the same instance of the action filter can be reused for different actions and if you have stored instance state in it it will probably break."
My custom attribute is apparently "break" ing. Thus began my search to answer the question …
How do you pass thread safe data between the methods of a System.Web.Http.Filters.ActionFilterAttribute?
An example is given in the post I referenced above of how data should be passed method to method using the HttpContext.Items dictionary. That's great and I can see how that would help but I'm not using ASP.net MVC'sSystem.Web.Http.Mvc.ActionFilterAttribute – which the poster uses in his answer. I'm doing Web API and the context object passed into the OnActionExecuting method is of type HttpActionContext and not of type ActionExecutingContext. I do not have access to the HttpContext.Items dictionary through the passed context, however, I believe that it is safe to access the HttpContext like this:
HttpContext.Current.Items[key]
Is that safe?
I do not have access to that dictionary in the constructor however and since that is where I receive my parameterized message string as a positional parameter, I am seemingly dependent on stored instance state.
So what to do?
In this post – also dependent on ASP.net MVC's System.Web.Http.Mvc.ActionFilterAttributeand its ActionExecutingContext– the poster uses the ActionParameters property of that context object to get at the parameters passed to the attribute, but I cannot find any equivalent in Web API's HttpActionContext. If I could, this would seem to be the answer! But alas…
How can I safely get to the positional parameter value passed into my constructor and the named parameter value passed in through a public property within the OnActionExecuting method?
Posts I have researched:
Are ActionFilterAttributes reused across threads? How does that work?
MVC Action Filter and Multiple Threads
passing action method parameter to ActionFilterAttribute in asp.net mvc
Why is my ASP.NET Web API ActionFilterAttribute OnActionExecuting not firing?
System.Web.Mvc.ActionFilterAttribute vs System.Web.Http.Filters.ActionFilterAttribute
Web Api 2 HttpContext or HttpActionContext
Background: In the constructor, I pass a parameterized message string that includes placeholders for the values of arguments passed to the method the attribute is applied to. I also have an optional LogAllResponses property that is set through a named parameter to the attribute that I use to decide how much information I will log. The public properties that receive these values are set through the constructor and attribute invocation like this:
[LogAction("Retrieve information for all ad week items with storeId: {storeId}.", LogAllResponses = false)]
The important parts of the implementation of my action filter appear below:
public class LogActionAttribute : ActionFilterAttribute
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public string ParameterizedMessage { get; set; }
public bool LogAllResponses { get; set; } = true;
public LogActionAttribute(string parameterizedMessage)
{
ParameterizedMessage = parameterizedMessage;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
HttpContext.Current.Items["__Parameterized_Message__"] = ParameterizedMessage;
HttpContext.Current.Items["__Log_All_Responses__"] = LogAllResponses.ToString();
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var contextualizedMessage = HttpContext.Current.Items["__Parameterized_Message__"] as string ?? "";
var logAllResponsesAsString = HttpContext.Current.Items["__Log_All_Responses__"] as string ?? "";
var logAllResponses = logAllResponsesAsString.CompareIgnoreCase("true") == 0;
// convert argument values with ID suffixes to identifiable names
var arguments = actionExecutedContext.ActionContext.ActionArguments;
//foreach (var arg in arguments)
// ...
// replace the placeholders in the parameterized message string with actual values
// log the contextualized message
//Log.Debug(...
base.OnActionExecuted(actionExecutedContext);
}
}

System.NullReferenceException creating viewModel

So, I'm trying to find the Umbraco node (as iPublishedContent), and pass it to the viewModel (as Ш've hijacked a route). So i put this in my controller:
private AddCouponCodesViewModel viewModel;
public AddCouponCodesController(){
//Get iPublished content
IPublishedContent content = Umbraco.TypedContent(1225);
//Pass to viewModel
viewModel = new AddCouponCodesViewModel(content);
RouteData.DataTokens["umbraco"] = content;
}
public ActionResult Index()
{
//return view etc
}
But I'm getting
Exception Details: System.NullReferenceException:
Object reference not set to an instance of an object.
here:
Source Error(AddCouponCodesViewModel.cs):
Line 20:
Line 21: }
Line 22: public AddCouponCodesViewModel(IPublishedContent content)
Line 23: : base(content)
Line 24: {
AddCouponCodeRenderModel.cs:
public class AddCouponCodesViewModel : RenderModel
{
public string test { get; set; }
public List<string> tables { get; set; }
public List<string> errors { get; set; }
public AddCouponCodesViewModel(IPublishedContent content, CultureInfo culture) : base(content, culture)
{
}
public AddCouponCodesViewModel(IPublishedContent content)
: base(content)
{
}
And this is the Global.asax
public class Global : UmbracoApplication
{
protected override void OnApplicationStarted(object sender, EventArgs e)
{
base.OnApplicationStarted(sender, e);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//AreaRegistration.RegisterAllAreas();
//WebApiConfig.Register(GlobalConfiguration.Configuration);
//FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
//RouteConfig.RegisterRoutes(RouteTable.Routes);
base.OnApplicationStarting(sender, e);
RouteTable.Routes.MapRoute(
"AddCouponCodes", // Route name
"Admin/{controller}/{action}/{id}", // URL with parameters
new { controller = "AddCouponCodes", action = "Index", id = "" } // Parameter defaults
);
}
}
The content is published (I've checked and double checked), and the node ID is correct.
What I'm basically trying to do here, is to get the route example.com/Admin/{controller}/{action}/{parameter}
To be routed, but having problems connecting it with the umbracoNode (And class RenderModel requires a iPublishContent object as a parameter, but I'm in no luck when trying to pass it anything)
Could someone please help me here, been stuck way too many hours on this :-(
To clarify, if you are hijacking a route, it means that you are overriding the way Umbraco passes it's RenderModel to one of it's published pages. You can either do this globally by overriding the main RenderMvcController, or you can override on a DocumentType-specific basis. So for example, if I have a Homepage doc type, I could create:
public HomepageController : RenderMvcController
{
public override ActionResult Index(RenderModel model)
{
// Create your new renderModel here, inheriting
// from RenderModel
return CurrentTemplate(renderModel);
}
}
This would route all calls to the homepage through this one action. For this, you don't need to define any new routes in the route table. And you should override the render model in the action not in the constructor.
Your question is slightly confusing and it's not entirely clear what you are trying to achieve because:
You have defined a route, and
In your constructor you are calling Umbraco.TypedContent(1225) to retrieve a specific published node
So ... if the admin page you are trying to route has itself been published by Umbraco (and it doesn't sound like it has), the just create a new controller with the name of the page's document type and override the render model in the way described above.
However ... if your admin page hasn't been published by Umbraco and you just want the admin page to access node data, then you have a couple of options:
Create a surface controller, inheriting from SurfaceController. This will give you access to the Umbraco context et al; or
Create a standard controller (preferrably in an Area) and inject the ContentCache using something like Autofac
E.g.:
builder.RegisterControllers(typeof (AdminController).Assembly)
.WithParameter("contentCache", UmbracoContext.Current.ContentCache);
Create a standard controller (preferrably in an Area) and access the node using Umbraco's ContentService API, i.e. new Umbraco.Core.Services.ContentService().GetById(1225)
The difference between the last two approaches is that:
Injecting the ContentCache provides you readonly but very quick access to the published content.
Accessing the ContentService provides you read/write access to the nodes themselves but at the expense of speed as you are querying the database directly.
It depends on what your requirement is.
Either way, it is well worth taking time to read through the documentation for hijacking Umbraco routes, and at least trying to understand what is going on.
Well, I can tell you that your view isn't getting fed anything for the Razor markup because your Index method doesn't feed it anything. That's one problem. I can also tell you, that in your AddCouponCodesViewModel, you'll need an empty constructor, so that the razor syntax can just create an instance, and then populate it to match your submitted object to the view.
Modify your ViewController :
public ActionResult Index()
{
return View(viewModel);
}
Modify your AddCouponCodesViewModel to add an Empty constructor:
public AddCouponCodesViewModel()
{
}
Create a paramaterless constructor on your view model like this:
public AddCouponCodesViewModel():
this(new UmbracoHelper(UmbracoContext.Current).
TypedContent(UmbracoContext.Current.PageId))
{
}
This will get the contexts your other constructors are looking for.
After you've created a class with specific constructors, the compiler stops generating a parameterless one by default. Since you need a parameterless constructor, this is how to get one and still pass in the Umbraco contextual info your viewmodel needs

Creating an "Ambient Context" (UserContext) for an ASP.NET application using a static factory Func<T>

I have found out that I need the current logged in user data in nearly every class (controllers, view, HTML helpers, services and so on). So I thought about to create an "Ambient Context" instead of injecting an IUserService or the User directly.
My approach looks something like that.
public class Bootstrapper
{
public void Boot()
{
var container = new Container();
// the call to IUserService.GetUser is cached per Http request
// by using a dynamic proxy caching mechanism, that also handles cases where we want to
// invalidate a cache within an Http request
UserContext.ConfigureUser = container.GetInstance<IUserService>().GetUser;
}
}
public interface IUserService
{
User GetUser();
}
public class User
{
string Name { get; set; }
}
public class UserContext : AbstractFactoryBase<User>
{
public static Func<User> ConfigureUser = NotConfigured;
public static User ActiveUser { get { return ConfigureUser(); } }
}
public class AbstractFactoryBase<T>
{
protected static T NotConfigured()
{
throw new Exception(String.Format("{0} is not configured", typeof(T).Name));
}
}
Example usage:
public class Controller
{
public ActionResult Index()
{
var activeUser = UserContext.ActiveUser;
return View();
}
}
Is my approach correct or do I missing something? Do you have better solutions in mind?
UPDATE:
More Detail of the User class:
public class User
{
string Name { get; set; }
bool IsSuperUser { get; set;}
IEnumerable<AzManOperation> Operations { get; set}
}
In Controllers we need to check if an User is a SuperUser to only provide the SuperUser some extra functionality.
public class BaseController : Controller
{
private readonly IUserService _userService;
BaseControler(IUserService userService)
{
_userService = userService
}
public User ActiveUser
{
get { return _userService.GetUser(); }
}
}
In Views we check Operations to only show an edit or delete button if the user has the right to do so. A view never uses the DependencyResolver, but ViewBag or ViewModel. My idea here is to implementing a custom ViewBasePage and providing an ActiveUser property, so that Views have an easy accesss.
In HtmlHelpers we render controls depending on IsSuperUser and Operations (passing in the User object or using DependencyResolver).
In Service Classes we need those properties too. For instance to decide if a basket is valid or not (check if the User is allowed to buy articles that are not in a standard list). So the Service class depends on IUserService and calling GetUser().
In Action Filters to force the user to change his password (only if it is not a SuperUser and User.ForcePasswordChange is true). Here we use the DependencyResolver.
My wish is to have a more easily way to get the User object, instead of using DependencyResolver.Current.GetService().GetUser() or using things like ViewBag.ActiveUser = User.
The User object is an object that is almost everywhere needed to check permissions or the like.
In Views we check Operations to only show an edit or delete button if the user has the right to do so.
The view should not do this check. The Controller should return a view model to the view that contains boolean properties that state whether those buttons should be visible. Returning a bool with IsSuperUser already moves to much knownledge into the view. The view shouldn't know that it should show a certain button for a super user: that's up to the controller. The view should only be told what to display.
If almost all views have this code, there are ways to extract repetitive parts out of your views, for instance with partial views. If you're finding yourself repeating those properties over many view models, perhaps you should define an envelope view model (a generic view model that wraps the specific model as T). A controller can create its view model, while you create a service or cross-cutting concern that wraps it in your envelope.
In Service Classes we need those properties too. For instance to decide if a basket is valid or not
In this case you are talking about validation, which is a cross-cutting concern. You should use decorators to add this behavior instead.
This is MVC, right?
You're reinventing the wheel.
Add this method to your Global.asax.cs:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
var ticket = FormsAuthentication.Decrypt(authCookie.Value);
var user = ticket.Name;
var identity = new GenericIdentity(user, "Forms");
var principal = new GenericPrincipal(identity, null);
Context.User = principal;
}
}
This example shows forms authentication which you can strip if you're using another mechanism. The key is these three lines:
var identity = new GenericIdentity(user, "Forms");
var principal = new GenericPrincipal(identity, null);
Context.User = principal;
GenericIdentity and GenericPrincipal can be replaced with anything you want as long as they implement the (trivial) IIdentity and IPrincipal interfaces. You can create your own implementations of these classes with whatever extra properties you need.
You can then access the authenticated user from all the things you listed - controllers, views, etc. - via HttpContext.Current.User (which is static).
If you created your own implementation of IPrincipal you can just cast that reference to your custom type.
You'll note that IPrincipal has a method called IsInRole, so you'd say:
if (HttpContext.Current.User.IsInRole("SuperUser"))
TL;DR - you are overengineering something ASP.NET has already solved, and I'd have an aneurysm if I saw the types you're proposing in a production application.
I think the easiest and maintainable solution is to create a static class CurrentUserProvider which has only one method Get(HttpContextBase) that returns the current user, behind the scene you can use the DependencyResolver to get the service that actually returns the user. Then where you need the CurrentUser you can call CurrentUserProvider.Get(context) and do whatever custom logic you need to perform.
The other solution that you are trying to do is injecting the service in the base controller constructor which is okay if you have handful of controllers, it would become an issue if you have quite a number of controllers and not all of the controllers requires that service. Writing tests for those controller would be such pain in the neck, because you have to create stubs/mocks for that service for all your controller tests. Maybe you can use property injection instead of constructor to address it.
You can use the same property injection for Filters too.
Now, the remaining two are the view and the helper. For View you can create special base class that inherits from WebViewPage/ViewPage and use the IViewActivator to inject the service and the same applies for the helpers, create helpers that inherits from system helpers and use those in your base controllers and views.
I think the second approach is bit cumbersome and it does not add that much value to do all those custom things.
So my suggestion is to go with the first.

ASP.NET Controller Base Class User.Identity.Name

As described in this post, I created an abstract base controller class in order to be able to pass data from a controller to master.page. In this case, I want to lookup a user in my db, querying for User.Identity.Name (only if he is logged in).
However, I noticed that in this abstract base class the User property is always null. What do I have to do to get this working?
Thanks a lot
As Paco suggested, the viewdata isn't initialized till after you are trying to use it.
Try overriding Controller.Initialize() instead:
public abstract class ApplicationController : Controller
{
private IUserRepository _repUser;
public ApplicationController()
{
}
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
_repUser = RepositoryFactory.getUserRepository();
var loggedInUser = _repUser.FindById(User.Identity.Name);
ViewData["LoggedInUser"] = loggedInUser;
}
}
To use the user, you should get the current page from
HttpContext.Current.User.Identity.Name
By setting authentication to Windows in web.config, you can get the user with User.Identity.Name
I use Page class on my static Utlilites classes. Like that;
Page P = (Page)HttpContext.Current.Handler;
and i can get all properties via the P object for the current requested page..
Have you tried this:
ControllerContext.HttpContext.Current.User.Identity.Name?

Categories

Resources