ASP.NET Core 2.2 - Action Filter db Query Question - asp.net-core-mvc

I have users in our app, who are mapped to companies. When a user logs in and starts to make requests I want a way to validate if that user is currently mapped to the company for access to company resources.
The idea I had was to create a whole controller just to manage all of this, but someone mentioned ActionFilters as a much better and cleaner option, I have to agree after looking at it.
The idea is to have the controller setup as:
controller - action - CompanyId - ReportId
So any request to root the system would just look up if there are any companies mapped to that logged in user.
But if the request included CompanyId then they'd go to that company's “portal” account page. It's really any request that includes CompanyId where I want the actionFilter to make a determination on if that user is allowed access.
Request comes in...
There is a CompanyId in the request!
ActionFilter:
Look up in db for all users assigned to that CompanyId. Is current user within that list? No? = kick'em out.
I tried to type in a code example, but the system told me to manually indent each line by 4 spaces, I was doing it from memory anyways so no idea how helpful it would have been anyways.

You could get your action parameters in your action filter and then get your database via HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>().Refer to here.
public class TestActionFilter:Attribute,IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
//If companyId is action parameter
var companyId= context.ActionArguments["companyId"].ToString();
//If companyId1 is query string
var companyId1= context.HttpContext.Request.Query["companyId1"].ToString();
//If companyId2 is in request header
var companyId2= context.HttpContext.Request.Headers["companyId2"].ToString();
//get your dbcontext
var db = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
//EF core logic
//...
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
You could use it on action directly using [TestActionFilter] attribute or set as global filter
services.AddMvc(options =>
{
options.Filters.Add(new TestActionFilter()); // an instance
});

Related

How to store PreRequestFilter information in AuthUserSession

I am building a web service using ServiceStack which has to support multiple vendors. The web service provides largely the same functionality to all vendors with some exceptions here and there.
In order to re-use as much functionality as possible I have come up with the following URL scheme:
http://localhost/brand1/templates
http://localhost/brand2/templates
"brand1" and "brand2" are not services but "templates" is. The Templates service's request DTO's will have a property called "Brand" like so:
[Route("/{Brand}/templates", "GET")]
public class GetTemplates
{
public Brand Brand { get; set; }
}
So in the Templates service I know which brand I am dealing with. This scheme works well.
What I cannot figure out though is this. The user of the service has to be authenticated and I cannot figure out how to handle the redirection of the service after the user has been authenticated since I have to pass along the brand information. I have created my own CustomAuthProvider class that inherits CredentialsAuthProvider. In the TryAuthenticate method I can set the authService.GetSession().ReferrerUrl property to the correct brand if I know what it was.
The only way I have found so far to get this information is to register a PreRequestFilter. My thinking here was that since the URL (e.g. http://localhost/brand1/templates) contains the brand I can store it in my own AuthUserSession class. I can't figure out how to do this. I have a "SessionFactory" method that I pass to the AuthFeature constructor. But what should I do in there? How do I get to the brand that I've obtained in the PreRequestFilter? Is it safe to store it in a field of the AppHost? I think not because of concurrency issues. How do I tie the PreRequestFilter to the SessionFactory method?
I hope I am explaining my problem clearly enough?
I was overthinking the solution because I didn't realize that I had all the information I needed in the IServiceBase parameter of the TryAuthenticate method of the CredentialsAuthProvider class.
In the end I came to the following solution:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
var session = authService.GetSession();
var origQuery = authService.Request.UrlReferrer.Query;
session.ReferrerUrl = "/error";
var queryString = origQuery.Substring(10); // strip "redirect="
var decodedUrl = HttpUtility.UrlDecode(queryString);
if (!string.IsNullOrWhiteSpace(decodedUrl))
{
var query = new Uri(decodedUrl);
session.ReferrerUrl = query.AbsolutePath;
}
return DoAuthentication(userName, password);
}
}
The different places where you can set the Url to redirect to during ServiceStack Authentication, in order of precedence are:
The Session.ReferrerUrl Url if it's populated
The Continue QueryString, FormData param when making the request to /auth (i.e Authenticate.Continue property)
The HTTP Referer HTTP Header
The CallbackUrl of the current AuthProvider used

Updating a session and using it within my view

I have a wish list, that is throughout the shopping pages. I need to know if this makes sense/the proper way of structuring.
Store the wish list as a session, when a user adds/deletes a new item it updates the session by an ajax call that just returns true/false if successful. On the partial view of the wish list component, I check for the session and cast it to my viewModel (which the session is based on) or serialize it for my knockout.
Let me know if this makes sense, otherwise I can post some code samples
It's hard to say without having a look at your basic structure, and not knowing you exact needs.
I don't know if you know this, but you can actually access the Session directly in Views:
#{
var wishlist = (WishList)HttpContext.Current.Session["Wishlist"];
}
It's fine to use Ajax to update it server side; and then you can return a partial view from the controller, to use however you like in the Ajax success call.
I hope this makes sense.
To begin with, if the wishlist is only supposed to exist for the duration of their visit then storing it in a session would be the best thing to do. However if the wishlist is supposed to live longer than a single visit and should be available to the user upon their return then I would suggest storing it in the database against the user's credentials/account (this is presuming they have an account).
As for the session itself, whilst you can access session data from a view I would not suggest it as you start to have a dependency on the session and before long you'll have code such as this scattered throughout your views.
var wishlist = (WishList)HttpContext.Current.Session["Wishlist"];
What happens when you want to change the way the wishlist works and instead have it database driven as you'd now like to persist the wishlist? You'll have to go through all of your views updating the references to the session.
Instead I would opt for registering your session with your IoC container of choice and injecting it using dependency injection, here is a simple example of how to register the session with StructureMap:
public class WebsiteRegistry : Registry
{
public WebsiteRegistry()
{
this.For<IUserWishlist>().HybridHttpOrThreadLocalScoped().Use(() => GetUserWishlistFromSession());
}
public static IUserWishlist GetUserWishlistFromSession()
{
var session = HttpContext.Current.Session;
if (session["WishList"] != null)
{
return session["WishList"] as IUserWishlist;
}
/* Create new empty session object */
session["WishList"] = new UserWishlist();
return session["WishList"] as IUserWishlist;
}
}
Now you're able to inject your wishlist into your controller and pass the data to your view via a view model. And as you're now programming against an interface instead of an implementation you could easily change how the wishlist is persisted without needing to change any code that references the wishlist.
public class WishlistController : Controller {
private readonly IUserWishlist userWishlist;
public void WishlistController(IUserWishlist userWishlist) {
this.userWishlist= userWishlist;
}
public ActionResult ViewProfile()
{
...
var viewModel = new UserWishlistViewModel {
wishlist = this.userWishlist.GetWishList()
}
...
}
}
I've written a more detailed example up in a blog post that might be of interest which can be read here. I hope this helps!

MVC3 "Global Variable"

I am creating an MVC Intranet app with windows authentication. The data being accessed is year specific, so I need a way to set a global variable, be it a session, or whatever, that must be set before any queries are performed.
I've not been able to find a way to force a user to login since they are already authorized via their windows login credentials so using attributes to force a login seems problematic.
I am finding that users could navigate to any particular page and attempt to pull data and create a bit of havoc without the year defined.
What I would like to do is force a user to a year select page to select the year which they wish to access if the year variable is not yet set.
I've checked related questions and they do not appear to provide answer to my current conundrum.
You can write a custom ActionFilter, which you put on all your controllers (but not on the action where you have to select the year, of course). If year ain't be selected, you redirect to the controller/action where you can choose the year.
public class CheckYearAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(<YEAR IS NOT SET>)//redirect
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {{ "Controller", "YourController" },
{ "Action", "YourAction" } });
}
base.OnActionExecuting(filterContext);
}
}

Global redirect based on logic in ASP.NET MVC3

I am building an ASP.NET MVC3 computer support ticketing portal.
There is a maintenance state, where it is best to forbid the users from interacting with EF/Database, to avoid "collisions" I am currently getting.
I have an IMaintenanceDispatcher that has a boolean property IsOnMaintenance set to true by the business logic, whenever a background logic puts the portal in that state.
I need to redirect client requests to a parking page for the time of maintenance.
Where do I place the logic that will check if the IsOnMaintenance is true, and if so, do a redirect to a URL?
You could put it in an ActionFilterAttribute and apply that attribute to any applicable actions/controllers or globally.
public class IsOnMaintenanceAttribute : ActionFilterAttribute
{
//You'll need to setup your IoC to inject this
public IMaintenanceDispatcher InjectedMaintenanceDispatcher { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
object ticketIdObj;
if (!filterContext.ActionParameters.TryGetValue("ticketId", out ticketIdObj))
return;
//Make sure it exists
if (InjectedMaintenanceDispatcher.IsOnMaintenance(int.parse(ticketIdObj)))
{
var routeValues = new RouteValueDictionary(new {
action = "parkingpage",
controller = "maintenance",
area = "ticket" });
filterContext.Result = new RedirectToRouteResult(routeValues);
return;
}
}
}
Note, your action method parameters needs to contain a variable named ticketId for the filterContext.ActionParameters.TryGetValue to work.
Note: I had assumed that an individual ticket is put into maintenance mode and you were wanting to check for that... but re-reading the question it seems like you want to put the whole area/site on hold. Even with that case, the ActionFilterAttribute example still holds... just not as different.

mvc3 OutputCache RemoveOutputCacheItem RenderAction

I did my research but haven't found any answers.
I'm using Html.RenderAction in a masterpage ( to render page header with links specific to user permissions ). Action is decorated with OutputCache, returns partial control and gets cached as expected.
When the event happens ( let's say permissions are changed ) I want to programmatically invalidate cached partial control.
I'm trying to use RemoveOutputCacheItem method. It takes a path as a parameter. I'm trying to set the path to the action used in Html.RenderAction. That doesn't invalidate the action.
How can I programmatically invalidate the action?
Thanks
The cache for child actions is stored in the OutputCacheAttribute.ChildActionCache property. The problem is that the API generating ids for child actions and storing them in this object is not public (WHY Microsoft??). So if you try to loop through the objects in this collection you will discover that it will also contain the cached value for your child action but you won't be able to identify it unless you reverse engineer the algorithm being used to generate keys which looks something like this (as seen with Reflector):
internal string GetChildActionUniqueId(ActionExecutingContext filterContext)
{
StringBuilder builder = new StringBuilder();
builder.Append("_MvcChildActionCache_");
builder.Append(filterContext.ActionDescriptor.UniqueId);
builder.Append(DescriptorUtil.CreateUniqueId(new object[] { this.VaryByCustom }));
if (!string.IsNullOrEmpty(this.VaryByCustom))
{
string varyByCustomString = filterContext.HttpContext.ApplicationInstance.GetVaryByCustomString(HttpContext.Current, this.VaryByCustom);
builder.Append(varyByCustomString);
}
builder.Append(GetUniqueIdFromActionParameters(filterContext, SplitVaryByParam(this.VaryByParam)));
using (SHA256 sha = SHA256.Create())
{
return Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString())));
}
}
So you could perform the following madness:
public ActionResult Invalidate()
{
OutputCacheAttribute.ChildActionCache = new MemoryCache("NewDefault");
return View();
}
which obviously will invalidate all cached child actions which might not be what you are looking for but I am afraid is the only way other than of course reverse engineering the key generation :-).
#Microsoft, please, I am begging you for ASP.NET MVC 4.0:
introduce the possibility to do donut caching in addition to donut hole caching
introduce the possibility to easily expire the result of a cached controller action (something more MVCish than Response.RemoveOutputCacheItem)
introduce the possibility to easily expire the result of a cached child action
if you do 1. then obviously introduce the possibility to expire the cached donut portion.
You might want to approach this a different way. You could create a custom AuthorizeAttribute -- it would simply allow everyone -- and add override the OnCacheValidation method to incorporate your logic. If the base OnCacheValidation returns HttpValidationStatus.Valid, then make your check to see if the state has changed and if so, return HttpValidationStatus.Invalid instead.
public class PermissionsChangeValidationAttribute : AuthorizeAttribute
{
public override OnAuthorization( AuthorizationContext filterContext )
{
base.OnAuthorization( filterContext );
}
public override HttpValidationStatus OnCacheAuthorization( HttpContextBase httpContext )
{
var status = base.OnCacheAuthorization( httpContext );
if (status == HttpValidationStatus.Valid)
{
... check if the permissions have changed somehow
if (changed)
{
status = HttpValidationStatus.Invalid;
}
}
return status;
}
}
Note that there are ways to pass additional data in the cache validation process if you need to track the previous state, but you'd have to replicate some code in the base class and add your own custom cache validation handler. You can mine some ideas on how to do this from my blog post on creating a custom authorize attribute: http://farm-fresh-code.blogspot.com/2011/03/revisiting-custom-authorization-in.html

Resources