I want to create connection between global.asax and my controller. We are using a database in our website. We have a code to check whether the database exists or not. If it doesn't not exist, we want to display a message like "database initializing" etc..
Database check in GLOBAL.ASAX :
public class MvcApplication : System.Web.HttpApplication
{
public static bool flag;
protected void Application_Start()
{
Database.SetInitializer<PhoneDexContext>(null);
var connString = ConfigurationManager.ConnectionStrings["DatabaseConnection"].ConnectionString;
using (PhoneDexContext db = new PhoneDexContext())
{
if (!db.Database.Exists())
{
flag = true;
db.Database.CreateIfNotExists();
}
}
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
protected void Application_BeginRequest()
{
if (flag)
{
Response.Redirect("Loading/LoadingScreen");
}
}
}
But we have a problem with using response redirect. It returns
{"Response is not available in this context."}.
We have a controller which is LoadingController and it has this code;
public class LoadingController : Controller
{
// GET: Loading
public ActionResult LoadingScreen()
{
return View();
}
}
But we can't jump to this part. How can i make connection?? Thanks
First, Application_Start does not handle any user requests. It is just perform some start up initialization. It invoked only once when app starts. To do some checks based on user's actions and properly respond you need to move these checks into Application_BeginRequest method.
Second, you also need to check if user already requesting /Loading/LoadScreen before responding with redirect to that page. Otherwise you will get an infinite redirects until database created.
public class MvcApplication : HttpApplication
{
private static bool dbInitialized;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// We can do it asynchronously to not block other initialization code.
Task.Run((Action)CreateDataBase);
}
private static void CreateDataBase()
{
Database.SetInitializer<PhoneDexContext>(null);
using (PhoneDexContext db = new PhoneDexContext())
{
if (!db.Database.Exists())
db.Database.CreateIfNotExists();
}
dbInitialized = true;
}
protected void Application_BeginRequest()
{
if (!dbInitialized && !this.Request.Url.LocalPath.StartsWith("/Loading/LoadingScreen", StringComparison.OrdinalIgnoreCase))
{
this.Response.Redirect("/Loading/LoadingScreen");
}
}
}
You can to further and move checks into the ActionFilter as you will able to work with RouteData and check action and controller parameters instead of url. Combined with nameof that would be less error-prone to route changes, renaming, refactoring and so on.
I think this answer will give you what you want: Return different views in a controller
In your case instead of the response.redirect use return View("/Loading/LoadScreen"); ...or something simular
try this
HttpContext.Current.Response.Redirect("/Loading/LoadScreen");
beacuse Response object is not avaliable in application_start method of global.asax.
you must use HttpContext.Current for any situation.
You can also use the 'Auto-Start feature' (How to warm up an ASP.NET MVC application on IIS 7.5? : Darins answer). This will execute once, before the website is ready to serve requests. The downside is you can't show the user a 'please wait' window. Personally I would not check at every request if the database is existent or not. Just execute it; the database update should not take very long I guess?
Application_Start happens before ASP.Net starts processing the request.
You could set a global static flag to indicate the error condition, then handle Application_BeginRequest and check the flag and redirect.
static bool _isDbLoaded;
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start(){
Database.SetInitializer<PhoneDexContext>(null);
var connString = ConfigurationManager.ConnectionStrings["DatabaseConnection"].ConnectionString;
using (PhoneDexContext db = new PhoneDexContext())
{
if (!db.Database.Exists())
{
_isDbLoaded = false;
db.Database.CreateIfNotExists();
}
}
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
protected void Application_BeginRequest(){
if(!_isDbLoaded){
Response.Redirect("Loading/LoadingPage");
}
}
Since Request and Response won't be available in your Application_Start event, You might consider having this code somewhere in the MVC request-response pipeline. An action filter is a good place.
public class VerifySetupIsGood : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var dbSetDate = context.HttpContext.Application["DbSetDate"] as string;
if (String.IsNullOrEmpty(dbSetDate))
{
//to do : Execute your custom code to check db existence here
var values = new Dictionary<string, string> { { "action", "LoadScreen" },
{ "controller", "Loading" } };
var r = new RouteValueDictionary(values);
//redirect the request to MissingDatabase action method.
context.Result = new RedirectToRouteResult(r);
}
base.OnActionExecuting(context);
}
}
Here we are first checking whether the application variable has a valid entry for "DbSetDate" key. By default, it will not be there. Then you have to execute your custom code to check whether your db exist. If not, Redirect to the LoadScreen action.
Register this filter globally so that it will be executed for any request coming to your application.
GlobalFilters.Filters.Add(new VerifySetupIsGood());
Now in when you are done with setting up your database, update this application variable to have a valid value.
HttpContext.Application["DbSetDate"] = DateTime.Now.ToString();
Remember, Application variable states will also gets reset. So don't just rely on that. You should run your custom code to check your db exists inside the if condition. If condition is to prevent your custom db check for every single request.
Related
I want to identify the one point which is hit every time before a request goes to the controller in the webAPI. I need to put in a custom authentication at that point. I am already doing a customAuthorization but I want to tweak in some custom authentication even before it reaches the controller.
The application_Start method only gets triggered once and so I am not quite sure what is the one place where the control goes every time we put in a URL in the browser and hit enter.
Gloabal.asax has more methods, which can be overloaded and one of them is Application_BeginRequest
And here's more detailed lifecycle. Controller factory also might help you intercepting and tweeking requests.
protected void Application_BeginRequest(object sender, EventArgs e) //Not triggered with PUT
{
//your code
}
You can opt for ActionFilterAttribute of Web API. This is triggered for every request that comes in.
Execution pipeline:
Controller Constructor > ActionFilter's OnActionExecuting > Controller action > ActionFilter's OnActionExecuted
Simple ActionFilterAttribute implementation:
public class YourFilterName : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
// pre-processing
//Your authentication logic goes here - use actionContext
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var objectContent = actionExecutedContext.Response.Content as ObjectContent;
if (objectContent != null)
{
var type = objectContent.ObjectType; //type of the returned object
var value = objectContent.Value; //holding the returned value
}
Debug.WriteLine("OnActionExecuted Response " + actionExecutedContext.Response.StatusCode.ToString());
}
}
I am building a ASP.NET Core MVC application and am trying to create a global action filter that logs how much time is spent executing an action (it should only log if spent time is above some threshold). I have succesfully done this but now I want to be able to say that a single action or a single controller should have a different threshold. When I try this, my action filter is applied twice(which is not what I want) but with the correct two different thresholds.
I have tried quite a few things and searched around. In an MVC 3 and an MVC 4 project I have successfully done this using RegisterGlobalFilters() in Global.asax and it automatically overrides the global one when I used the attribute on a controller/action. I have also tried the approach listed in this post, without luck:
Override global authorize filter in ASP.NET Core MVC 1.0
My code for my ActionFilterAttribute:
public class PerformanceLoggingAttribute : ActionFilterAttribute
{
public int ExpectedMax = -1; // Log everything unless this is explicitly set
private Stopwatch sw;
public override void OnActionExecuting(ActionExecutingContext context)
{
sw = Stopwatch.StartNew();
}
public override void OnActionExecuted(ActionExecutedContext context)
{
sw.Stop();
if (sw.ElapsedMilliseconds >= ExpectedMax)
{
// Log here
}
}
//public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
//{
// // If there is another performance filter, do nothing
// if (context.Filters.Any(item => item is PerformanceLoggingAttribute && item != this))
// {
// return Task.FromResult(0);
// }
// return base.OnActionExecutionAsync(context, next);
//}
}
I am applying this global filter in my Startup.cs:
services.AddMvc(options =>
{
if (_env.IsProduction()) options.Filters.Add(new RequireHttpsAttribute());
//options.Filters.Add(new PerformanceLoggingFilter() { ExpectedMax = 1 }); // Add Performance Logging filter
options.Filters.Add(new PerformanceLoggingAttribute() { ExpectedMax = 1 }); // Add Performance Logging filter
});
And in my controller I am applying the attribute:
//[TypeFilter(typeof(PerformanceLoggingFilter))]
[PerformanceLogging(ExpectedMax = 2)]
public IActionResult Index()
{
var vm = _performanceBuilder.BuildPerformanceViewModel();
return View(vm);
}
As you can tell from the code snippets above I have tried the OnActionExecutionAsync approach and I have also tried a IActionFilter instead and using [TypeFilter(typeof(PerformanceLoggingFilter))] on actions, but no luck.
Can anyone help me out?
May suggest you a bit different implementation of what you try to achieve by using one action filter and additional custom attribute:
create a new simple attribute (let's name it ExpectedMaxAttribute), that just holds the ExpectedMax value. Apply this attribute to controller's actions with different values.
keep your PerformanceLogging action filter as global, but modify implementation. On OnActionExecuted method check if controller's action has ExpectedMaxAttribute. If yes, then read ExpectedMax value from attribute, otherwise use the default value from the action filter.
Also, I recommend you to rename action filter accordingly to convention naming something like PerformanceLoggingActionFilter.
I got it working thanks to #Set's answer above in combination with this answer:
https://stackoverflow.com/a/36932793/5762645
I ended up with a global action that is applied to all actions and then having a simple ExpectedMaxAttribute that I put on actions where the threshold should be different. In the OnActionExecuted of my global action filter, I then check if the action in question has the ExpectedMaxAttribute attached to it and then read the ExpectedMax from that. Below is my attribute:
public class PerformanceLoggingExpectedMaxAttribute : ActionFilterAttribute
{
public int ExpectedMax = -1;
}
And the OnActionExecuted part that I added to my ActionFilter:
public override void OnActionExecuted(ActionExecutedContext context)
{
sw.Stop();
foreach (var filterDescriptor in context.ActionDescriptor.FilterDescriptors)
{
if (filterDescriptor.Filter is PerformanceLoggingExpectedMaxAttribute)
{
var expectedMaxAttribute = filterDescriptor.Filter as PerformanceLoggingExpectedMaxAttribute;
if (expectedMaxAttribute != null) ExpectedMax = expectedMaxAttribute.ExpectedMax;
break;
}
}
if (sw.ElapsedMilliseconds >= ExpectedMax)
{
_logger.LogInformation("Test log from PerformanceLoggingActionFilter");
}
}
i have two mvc applications in one solution.
now i need to maintain session when user redirects from one application to another.
so what my logic is,
passed GUID in URL.
get GUID in another projects Global.asax file using Init() method.
Log in another user.
i done whole code, added below.
now, i am getting call in Init() method but it also calls other methods which are passed in URl.
i.e. call becomes asynch, so because of that, user redirected to other page.
do i need to change my logic or just code?
below is my Global.asax file code.
public override void Init()
{
var userId = Guid.Parse(Request["UserGUID"].ToString());
if (userId != null && userId == Guid.Parse("1B541D9A-AC3E-466F-897B-6F9033F4533C"))
//my logic of login management
}
After more then 8 hours of R&D, i found solution that really helped me.
Actually my way is proper but the method is not right.
Application_AcquireRequestState
is the proper method for my scenario.
my code is :
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
public void Application_AcquireRequestState(object sender, EventArgs e)
{
var userId = HttpContext.Current.Request["UserGUID"];
if (userId != null)
{
Session["UserGuid"] = Guid.Parse(userId.ToString());
//My logic for session handling..
}
}
}
I have a custom filter:
public class SetAuthFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
//do something
}
}
In the Application_Start() under the Global.asax:
GlobalFilters.Filters.Add(new SetAuthFilter());
The above codes will be called everytime an action is invoked.
However, in my _Layout.cshtml, I have 2 different "BaseController", something like:
#Html.Action("SomeAction", "Base")
#Html.Action("SomeAction", "Base2")
When I set a break point, it appears that the SetAuthFilter is always being called three times, first before the page was launched, then second time when my break point hits the BaseController, then finally the third time when my break point hits the Base2Controller.
What should I do in order to avoid SetAuthFilter being called multiple times?
You simply cannot prevent it from being called if there are multiple actions that are interacting with the filter. It will be called every single request. However, you could cache your last request for that user's identity and, if it is the same request, immediately return without continuing onto the heavier authorization checks.
public class SetAuthFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var key = CreateKey(filterContext);
var isCached = HttpRuntime.Cache.Get(key);
if (isCached != null) return;
HttpRuntime.Cache.Insert(key, true);
// Heavy auth logic here
}
private string CreateKey(AuthorizationContext context)
{
// Or create some other unique key that allows you to identify
// the same request
return
context.RequestContext.HttpContext.User.Identity.Name + "|" +
context.RequestContext.HttpContext.Request.Url.AbsoluteUri;
}
}
Note that this doesn't account for null identities or bad URIs. You'd want to add some additional defensive checks as well as a cache invalidation strategy.
This will not allow you to bypass your authentication, since each unique request will still need to be validated. However, it minimizes the number of times you call expensive authorization logic.
For every secure controller action, the OnAuthorization overload will get called.
If you dont want that to happen, you should decorate your function with AllowAnonymous attribute.
If you don't want to call custom filter on each method:
Then remove the following line from Application_Start() under the Global.asax:
GlobalFilters.Filters.Add(new SetAuthFilter());
Add [SetAuth] attribute as follows on those methods and Controllers which really needs authorization filter :
[SetAuth]
public ActionResult Index()
{
// your code
return View(yourModel);
}
In my asp.net mvc3 application, I have a custom Authorization Attribute as seen below.
public class CustomAuthorize : AuthorizeAttribute
{
public IAccountRepository AccountRepository { get; set; }
public CustomAuthorize()
{
this.AccountRepository = new UserModel();
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
base.AuthorizeCore(httpContext);
return AccountRepository.isEnabled(HttpContext.Current.User.Identity.Name);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
}
}
I have the [CustomAuthorize] tag on my controller actions, and the AuthorizeCore method works fine - it performs the logic I want it to (making sure the account is actually enabled), and then returning as such.
However, the overridden HandleUnauthorizedRequest method, which as I understand it should allow me to control the behaviour of an unauthorized request, is not running at all. I put a breakpoint there, I put code in there, I access my application unauthorized, and the code never runs.
What am I missing?
EDIT: I did some more research and found a few other people who had this problem, but no solution unfortunately.
EDIT2: Sample code
[CustomAuthorize]
public class UserController: Controller
{
public UserController()
{
//do stuff here
}
}
EDIT 3: #Fabio
Here's what I'm trying to do. I have a login page (forms auth) that works fine - it calls my custom login, and then calls my AuthorizeCore override. My application uses a large amount of ajax calls, and my eventual goal is for whenever a user is using the application, and the administrator disables them, making an ajax call after being disabled (though still being logged in) should log them out. However, in order to do this, i want to return a custom response if the user is making an ajax call, and for that, I need to ovverride HandleUnauthorizedRequest. But my Authorize Core (and by extension HandleUnauthorizedRequest) are being ignored if the user is logged in (despite the fact that I have customauthorize tags on all of my controller actions that the ajax is calling).
In short: I want to authorize the user on every request, not just the login request (which seems to be what the membership provider is doing right now)
I ended up changing my approach a fair bit. I implemented individual permissions checking, and then that caused AuthorizeCore to be called every time (and not be cached, which I guess was what was happening before).
Interestingly enough, putting a breakpoint on the HandleUnauthorizedRequest override still doesn't break, but putting it inside the method will. Strange, and threw me off for a bit, but I've solved it now.
Code if anyone is interested:
public class CustomAuthorize : AuthorizeAttribute
{
public string Permissions { get; set; }
private IAccountRepository AccountRepository { get; set; }
private string[] permArray { get; set; }
private string reqStatus { get; set; }
public CustomAuthorize()
{
this.AccountRepository = new UserModel();
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
base.AuthorizeCore(httpContext);
if (Permissions != null) {
permArray = Permissions.Trim().Split(' ');
if (AccountRepository.isEnabled(httpContext.User.Identity.Name)) {
this.reqStatus = "permission";
return AccountRepository.hasPermissions(permArray);
} else {
return false;
}
} else {
return AccountRepository.isEnabled(httpContext.User.Identity.Name);
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (this.reqStatus == "permission") {
filterContext.Result = new RedirectResult(MvcApplication.eM.cause("no_permission", "redirect"));
} else {
base.HandleUnauthorizedRequest(filterContext);
}
}
}
And then I decorated the controller with this:
[CustomAuthorize(Permissions="test_perm")]
This may be a stupid answer/question but is AccountRepository.isEnabled method returning false so that the HandleUnauthorizedRequest can be executed?
If it's returning true, then the HandleUnauthorizedRequest method won't be executed.