ASP.NET MVC3, How to Use Different OutputCacheProfile to Different User Role? - asp.net-mvc-3

I know I can setup OutputCacheProfiles at web.config file.
I like to know how to apply different cache profile to different user role on page (controller) level?

You can decorate a controller with the OutputCache attribute which allows arguments to be passed as parameters. For example;
[OutputCache(Duration = 3600, VaryByParam = "None")]
There is no reason why you couldn't extend the attribute to take a further argument "RoleName" and perform a "Roles.IsUserInRole(RoleName)" and load different settings based upon each role.
EDIT
After comments from the author, I have reviewed my solution.
Firstly, you can define you cache profiles within the Web.config;
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<clear />
<add name="Default" duration="60" />
<add name="Admin" duration="10" />
</outputCacheProfiles>
</outputCacheSettings>
</caching>
I have extended the OutputCacheAttribute to account for authorisation of a user, and if the user authenticates, it loads that CacheProfile;
public class AuthorisedOutputCache : OutputCacheAttribute
{
public string RoleName { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Default Profile.
CacheProfile = "Default";
if (HttpContext.Current.Request.IsAuthenticated)
{
if (Roles.IsUserInRole(RoleName))
{
CacheProfile = RoleName;
}
}
base.OnActionExecuting(filterContext);
}
}
Here is the Index.cshtml file for completeness;
#model DateTime
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
The time is #Model.TimeOfDay.ToString()
</p>
Note: You will have to make sure to define a cacheprofile for each of your roles, aswell as a default for when no role is found.
EDIT
The author wished to know how to set the cache profile within the controller, I have posted a viable solution, but I don't like it because of the use of HttpContext.Items - so if anyone can suggest alternatives?
Firstly, you must change the OnActionExecuting to OnActionExecuted;
public class AuthorisedOutputCache : OutputCacheAttribute
{
public string RoleName { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
// Do you wish to force the profile?
if (HttpContext.Current.Items["Cache.Force"] != null)
{
// Force the profile and remove the flag.
CacheProfile = HttpContext.Current.Items["Cache.Force"].ToString();
HttpContext.Current.Items.Remove("Cache.Force");
}
else
{
// If the profile has not been set - use the role based authorisation -
// otherwise, carry on as normal.
if (string.IsNullOrEmpty(CacheProfile))
{
CacheProfile = "Default";
if (HttpContext.Current.Request.IsAuthenticated)
{
if (Roles.IsUserInRole(RoleName))
{
CacheProfile = "Admin";
}
}
}
}
base.OnActionExecuted(filterContext);
}
}
The following line allows you to set the profile within the controller;
public ActionResult Index()
{
// Forces the cache profile to one of the name of "Mandatory".
HttpContext.Items["Cache.Force"] = "Mandatory";
return View(IndexViewName, DateTime.Now);
}
Let me know if I can be of further assistance,
Matt

Related

ASP.NET Boilerplate: What is the best solution for Four eyes principle

I want to add 4 eyes principle to ASP.NET Boilerplate framework. That means every change on Role, User,.. need to be approved (by another admin) before applied to the system. I have searched for some time but no answer. So what is the best solution for this flow?
Can I create the same tables with Abp tables (dbo.AbpUser_Temp, etc) and the all the changes will be stored in these tables? Is there any better solution?
Example: In the application, Admin1 has created a user named User1. But this user cannot login to the application until he was approved by Admin2.
Simple Workflows
Example: In the application, Admin1 has created a user named User1. But this user cannot login to the application until he was approved by Admin2.
Simple workflows like these can be appropriately handled by a property and a method:
public class User : AbpUser<User>
{
public bool IsApproved { get; set; }
public void Approve(User approver)
{
if (approver.Id != CreatorUserId)
{
IsApproved = true;
}
}
}
Complex Workflows
Complex workflows like "every change" can do this instead of _Temp tables:
public abstract class ChangeBase : Entity<long>, IExtendableObject
{
public string EntityTypeAssemblyQualifiedName { get; set; }
public string EntityIdJsonString { get; set; }
public long ProposerUserId { get; set; }
public long? ApproverUserId { get; set; }
public string ExtensionData { get; set; }
}
public class Change : ChangeBase
{
[NotMapped]
public Type EntityType => Type.GetType(EntityTypeAssemblyQualifiedName);
[NotMapped]
public object EntityId => JsonConvert.DeserializeObject(EntityIdJsonString, EntityHelper.GetPrimaryKeyType(EntityType));
[NotMapped]
public bool IsApproved => ApproverUserId.HasValue && ApproverUserId != ProposerUserId;
[NotMapped]
public IDictionary<string, string> ChangedPropertyValuePairs => JObject.Parse(ExtensionData).ToObject<Dictionary<string, string>>();
public Change(EntityIdentifier changedEntityIdentifier, long proposerUserId, IDictionary<string, string> changedPropertyValuePairs)
{
EntityTypeAssemblyQualifiedName = changedEntityIdentifier.Type.AssemblyQualifiedName;
EntityIdJsonString = changedEntityIdentifier.Id.ToJsonString();
ProposerUserId = proposerUserId;
ExtensionData = JObject.FromObject(changedPropertyValuePairs).ToString(Formatting.None);
}
public bool Approve(long approverUserId)
{
if (approverUserId != ProposerUserId)
{
ApproverUserId = approverUserId;
return true;
}
return false;
}
}
Usage:
public class UserAppService // ...
{
private readonly IRepository<Change, long> _changeRepository;
public UserAppService(
IRepository<User, long> repository,
IRepository<Change, long> changeRepository) // : base(repository)
{
_changeRepository = changeRepository;
}
public void ChangeUserName(long userId, string newUserName)
{
// Validation, etc.
var changedPropertyValuePairs = new Dictionary<string, string> {
{ nameof(User.UserName), newUserName }
};
var change = new Change(
new EntityIdentifier(typeof(User), userId),
AbpSession.GetUserId(),
changedPropertyValuePairs
);
_changeRepository.Insert(change);
}
public void ApproveChange(long changeId)
{
// Validation, etc.
var change = _changeRepository.Get(changeId);
if (change.EntityType == typeof(User) && change.Approve(AbpSession.GetUserId()))
{
var user = Repository.Get((long)change.EntityId);
var changedPropertyValuePairs = change.ChangedPropertyValuePairs;
foreach (var changedProperty in changedPropertyValuePairs.Keys)
{
switch (changedProperty)
{
case nameof(User.UserName):
user.UserName = changedPropertyValuePairs[changedProperty];
break;
// ...
default:
break;
}
}
}
}
For development
Separate staging and production environments. Develop on one box, test it, get it reviewed and then deploy to a production box. Simple, effective and language agnostic advice.
Since ASP.NET Boilerplate framework included Entity Framework. You could also leverage migrations.
After you do your development work, and requires you to "update-database", then your SOP should be to have the admin review the (relatively simple) migrations that will be committed.
I hope that helps.
For application flow
There are probably quite a few ways to actually implement this so I'll cover a simple one get your idea's flowing, but keep in mind: The way you need to implement two person integrity must fit how your operating procedures should work, and not the other way around. Development doesn't drive business operations, business use-cases drive development.
Extending existing Identity* classes. Example: The ApplicationUser class (it may be named differently, but it derives from IdentityUser
Create 2 flags (boolean fields) that must be, and can only be turned 'on' by an administrator
a single administrator can only turn on 1 flag. (Which means you also have to store which administrator turned on which flag.)
The flags can be stored in the existing Abp* tables, or you can create a new table
Add logic so that the user is not allowed to log in unless those 2 flags are both on.
Example: default IdentityUserRole has identified and registered, but can not log in. Once both admin's switch the flags on, elevate the users IdentityUserRole to a role that is allowed to log in.

Access action method parameters from custom Authorize attribute in MVC 3

I am writing an MVC 3 app where users will be able to log in and manage their data. I want to prevent users from viewing or tampering with other user's data. My first instinct was to just verify access to the relevant object in each action method like this:
public ActionResult ShowDetails(int objectId)
{
DetailObject detail = _repo.GetById(objectId);
if (detail.User.UserID != (Guid)Membership.GetUser().ProviderUserKey)
{
return RedirectToAction("LogOff", "Account");
}
}
This works fine, but I thought it might be better to put the object authorization code into a custom Authorize attribute derived from AuthorizeAttribute, which I could then apply to the controller. Unfortunately, I have not been able to find a way to access the action method parameters from within my custom Authorize attribute. Instead, the only way I have found to access the incoming objectId is by examining httpContext.Request or filterContext.RequestContext.RouteData.Values:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private int _objectId = 0;
private IUnitOfWork _unitOfWork;
public MyAuthorizeAttribute(IUnitOfWork uow)
{
_unitOfWork = uow;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
int.TryParse((string) filterContext.RequestContext.RouteData.Values["id"], out _objectId);
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
int objectId = 0;
if (httpContext.Request.Params.AllKeys.Contains("id", StringComparer.InvariantCultureIgnoreCase))
{
int.TryParse(httpContext.Request[idKey], out objectId);
}
if (objectId != 0)
{
if (!IsAuthorized(objectId, httpContext.User.Identity.Name))
{
return false;
}
}
if (_objectId != 0)
{
if (!IsAuthorized(objectId, httpContext.User.Identity.Name))
{
return false;
}
}
return base.AuthorizeCore(httpContext);
}
private bool IsAuthorized(int objectId, string userName)
{
DetailObject detail;
detail = _unitOfWork.ObjectRepository.GetById(objectId);
if (detail == null)
{
return false;
}
if (userName != detail.User.UserName)
{
return false;
}
return true;
}
}
I find this approach to be very clunky. I really don't want to have to poke around in the RouteData or Request objects; it would be much cleaner to be able to access the action method parameters since model binding would have already pulled out the relevant data from the RouteData and Request.
I know I can access action method parameters from a custom Action Filter (as detailed here), but shouldn't data authorization code be placed in an Authorize Filter? The more examples I see of Authorize filters, the more I get the impression that they are intended only to handle roles.
My main question is: How do I access action method parameters from my custom Authorize Attribute?
Answer to your main question: no, unfortunately AuthorizationContext does not provide access to action parameters.
First off, you could use ValueProvider to not have to deal with whether the id is part of the route or a query parameter or HTTP posted, as follows:
public override void OnAuthorization(AuthorizationContext filterContext)
{
string id = filterContext.Controller.ValueProvider.GetValue("id").AttemptedValue;
...
}
This works for simple data types and introduces little overhead. However once you start using custom model binders for your action parameters, you have to inherit your filter from ActionFilterAttribute to avoid double binding:
[MyFilter]
public ActionResult MyAction([ModelBinder(typeof(MyModelBinder))] MyModel model)
{
...
}
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = filterContext.ActionParameters["model"] as MyModel;
...
}
}
While semantically inheriting from AuthorizeAttribute for authorization purposes sounds better, there are no other reasons for doing this. Moreover, I find using ActionFilterAttribute easier, as all you have to do is override only one method, not keeping a state for subsequent methods.

RoleProvider dosn't work with custom IIdentity and IPrincipal on server

I'm using a custom IIdentity and IPrincipal in my ASP.NET MVC application via EF 4.3 as expalined here (and follow accepted answer's solution). Also, I have a custom RoleProvider. In local (using IIS Express), it works currectly. But now, when I upload the application on a real host, it seems all users are in "admin" role! e.g. I create a user that is not in role "admin", but it can access to all protected pages (that need "admin" role). e.g. Role.IsUserInRole always returns true. Have you any idea please? Can you help me? Is there any setting that I should to do in IIS?
I explain that solution and it works for me. I don't now, may be you should rollback to the AuthenticateRequest event.If you want to try this way, you have to remove RoleManagerModule completely from your project. Try this and let me know if works or nop:
// in your module:
public void Init(HttpApplication context) {
_application = context;
// rollback this line:
_application.AuthenticateRequest += ApplicationAuthenticateRequest;
}
// and in web.config
<!-- in system.web section: -->
</system.web>
<!-- other stufs -->
<httpModules>
<remove name="RoleManager"/>
</httpModules>
</system.web>
<!-- and in system.webServer section: -->
<system.webServer>
<!-- other stufs -->
<modules runAllManagedModulesForAllRequests="true">
<remove name="RoleManager"/>
</modules>
<system.webServer>
If you want to keep using the default RoleManager, it gets difficult. I tried creating my own RoleManager by deriving from the default, without any luck.
After 2 days trying several things, I ended up creating some extension methods for RolePrincipal:
public static bool IsEmployee(this RolePrincipal principal)
{
if (IsAuthenticated())
return principal.IsInRole("Employee");
return false;
}
public static bool IsAdmin(this RolePrincipal principal)
{
if (IsAuthenticated())
return principal.IsInRole("Admin");
return false;
}
Created a new WebViewPage class:
public abstract class BaseViewPage : WebViewPage
{
public virtual new RolePrincipal User
{
get
{
if (base.User == null)
return null;
return (RolePrincipal)base.User; //Hard casting: If it goes wrong, it better goes wrong here
}
}
}
public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
public virtual new RolePrincipal User
{
get
{
if (base.User == null)
return null;
return (RolePrincipal)base.User; //Hard casting: If it goes wrong, it better goes wrong here
}
}
}
Modified the web.config in the views folder:
<pages pageBaseType="MyCompany.MyProject.BaseViewPage">
And all my Controllers derive from my BaseController:
public abstract class BaseController : Controller
{
protected virtual new RolePrincipal User
{
get { return HttpContext.User as RolePrincipal; }
}
}
Downside is that the methods query my database everytime they get called.
I'm using MVC 4 btw
Hope this helps anyone

MVC3 load common data for views

I am developing an MVC3 "movie list" application containing several "sites" depending on the request hostname.
I am trying to use a strongly typed ViewModel like this (examples are simplified to get to the essence of the question):
class ViewModelBase
{
public int siteId { get; private set; }
public ViewModelBase(DbContext db)
{
siteId = <here I want to make a db-lookup based on the request hostname> <== This is my problem
}
}
class MoviesIndexViewModel : ViewModelBase
{
public List<Movie> movies { get; private set; }
public MoviesIndexViewModel(DbContext db) : base(db)
{
movies = db.Movies.where(m => m.SiteId == siteId).ToList();
}
}
An my controller would then just do this:
public class MoviesController : Controller
{
public ActionResult Index()
{
var model = new MoviesIndexViewModel(new MySpecialDbContext());
return View(model);
}
}
Question is: How will I get the "request host header" into the code line shown above? I know how to make the actual DB-lookup, but can I just access any request parameters here? Or should I supply something through parameters to the constructor?
I would not use Dbcontext in my view models. Read about Separation of concerns
Instead, use OnResultExecuting in your BaseController to add the common data:
protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
var baseModel = filterContext.Controller.ViewData.Model as YourCustomModel;
if (baseModel != null)
{
// call a repository or whatever to add information to the model.
}
base.OnResultExecuting(filterContext);
}
Update
yes. The controller is the glue between the "model" (repositores, webservices or any other data source) and the view. The ViewModel is just an abstraction to move away logic from the view.
Here is the three main reasons you should use a view model:
http://blog.gauffin.org/2011/07/three-reasons-to-why-you-should-use-view-models/
And an alternative approach to handle common view data: http://blog.gauffin.org/2011/09/getting-information-into-the-layout-without-using-viewbag/

Using Unity with ASP.NET MVC 3 for an object stored in Session

I've got an MVC controller that stores an object in Session. Different controller actions retrieve the object from Session, do stuff with it, and save it back.
I'd like to use Unity so that the controller just deals with an interface, but I'm not sure how to achieve this (I'm fairly new to the whole dependency injection thing). Here's some sample code:
public class MyController : Controller
{
[HttpGet]
public ActionResult Index()
{
var state = new State();
// do stuff with state
Session[Key] = state;
return View();
}
[HttpPost]
public ActionResult Change()
{
var state = Session[Key] as State;
// do stuff with state
Session[Key] = state;
return View();
}
}
So basically I want to use IState instead of State. But where/how does Unity inject the concrete implementation? It seems like it can't happen in the constructor, because I only need to instantiate a new object in the Index() action. Is there maybe some magic way I can add a parameter to Index() that Unity can use?
If you want to use Unity you must change your implementation little bit. You must define your controller as:
public class MyController : Controller
{
private IState _state;
public MyController(IState state)
{
_state = state;
}
[HttpGet]
public ActionResult Index()
{
// Fill the state but you cannot change instance!
_state.A = ...;
_state.B = ...;
return View();
}
[HttpPost]
public ActionResult Change()
{
// Fill the state but you cannot change instance!
_state.A = ...;
_state.B = ...;
return View();
}
}
Now you need two additional step. You must use PerSessionLifetime manager for your IState resolving and you must configure Unity to resolve controllers and their dependencies - there is some build in support for resolving in ASP.NET MVC 3.
Unity doesn't provide PerSessionLifetime manager so you must build your own.
public class PerSessionLifetimeManager : LifetimeManager
{
private readonly Guid _key = Guid.NewGuid();
public override object GetValue()
{
return HttpContext.Current.Session[_key];
}
public override void SetValue(object newValue)
{
HttpContext.Current.Session[_key] = newValue;
}
public override void RemoveValue()
{
HttpContext.Current.Session.Remove(_key);
}
}
You can use this lifetime when configuring controller or you can configure extension in unity configuration and define your IState:
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<alias alias="perSession" type="NamespaceName.PerSessionLifetimeManager, AssemblyName"/>
<alias alias="IState" type="NamespaceName.IState, AssemblyName" />
<alias alias="State" type="NamespaceName.State, AssemblyName" />
<container name="Web">
<register type="IState" mapTo="State" >
<lifetime type="perSession" />
</register>
</container>
</unity>
</configuration>
You might want to do this with an ActionFilter. The ActionFilter could grab the object from session state (instantiating it if needed), and add it to your ActionParameters collection.
public class IncludeStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(...)
{
var currentState = filterContext.HttpContext.Session[Key] as State;
if (currentState == null)
{
currentState = new State();
}
filterContext.ActionParameters["state"] = currentState
}
public override void OnActionExecuted(...)
{
filterContext.HttpContext.Session[Key] = filterContext.ActionParameters["state"];
}
}
Then, your Index action looks like this:
[HttpGet]
[IncludeState]
public ActionResult Index(State state)
{
// do stuff with state
return View();
}
The only thing I'm unsure about is where your key comes from.
I realize that this doesn't use Unity, but maybe you don't need it.

Resources