9

In my app, I am checking if some config file is available or not, if it's not then I want to redirect to install page.

To me the best place to accomplish this is application_start. Because it's happening for only one time. If I do the checking in application_start and write Response.Redirect I will get Response is not available in this context.

I tried other answers in stack overflow to redirect in application_start like HttpContext.Current.Response.Redirect; none worked for me.

I don't want to do it in a base controller or a filter because the checking logic will happen for every single request.

My goal is to check it only once and it's best to be when the app start.

Update 1

I added response.redirect to the application_start but got error like this:

application start:

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        Response.RedirectToRoute(
            new RouteValueDictionary {
            { "Controller", "Home" },
            { "Action", "about" }
        });
    }

but i am receiving an error like this:

Response is not available in this context.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Web.HttpException: Response is not available in this context.

Community
  • 1
  • 1
Mohammad Hossein Amri
  • 1,842
  • 2
  • 23
  • 43
  • 3
    Request/Response is not available in `Application_Start` http://stackoverflow.com/questions/5750030/request-object-in-application-start-event – Nkosi Aug 10 '16 at 19:56
  • If and what version of IIS are you using? – Nkosi Aug 10 '16 at 20:25
  • you mean to check something for every user everytime or onces in life time or what exactly? It's not really clear what you are trying to do here. Please explain further. – Hassen Ch. Aug 16 '16 at 08:57
  • @HassenCh. for sure install process should happen only once if the app meet some condition – Mohammad Hossein Amri Aug 17 '16 at 19:45

3 Answers3

3

If you really want to avoid having a filter run for every request after setup then you can do something like this:

RedirectAttribute.cs (generic example)

public class RedirectAttribute : ActionFilterAttribute
{
    private readonly string _controller;
    private readonly string _action;

    public RedirectAttribute(string controller, string action)
    {
        _controller = controller;
        _action = action;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.ActionDescriptor.ActionName != _action ||
            filterContext.ActionDescriptor.ControllerDescriptor.ControllerName != _controller)
        {
            filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary(new {controller = _controller, action = _action})
                );
        }
    }
}

In Global.asax.cs above "FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);"

if (/*Insert logic to check if the config file does NOT exist*/)
{
    //Replace "Setup" and "Index" with your setup controller and action below
    GlobalFilters.Filters.Add(new RedirectAttribute("Setup", "Index"));
}

Now, after your user has fully completed setup, you can unload the app domain:

HttpRuntime.UnloadAppDomain();

Please note: you will need to make sure that your app has permission to unload the AppDomain. If it does not, you can try File.SetLastWriteTimeUtc(...) on the configuration file (AppDomain.CurrentDomain.SetupInformation.ConfigurationFile.) This will also unload the AppDomain.

Unloading the AppDomain will "restart" the web app and call Application_Start() again. The filter will not be added to your requests since your if statement will determine that the app has already been configured.

agriffin
  • 541
  • 5
  • 10
0

As a workaround, you could use lazy initialization in a static variable inside a filter. The actual file operations to check for the config file will only happen once during the first request. After that the value of the check for the config file is saved in the static Lazy variable. As an added bonus it's also threadsafe.

In the end the check still happens on every request, but the operation is fast after the initial check because the result of the check is saved in memory.

public class ConfigFileCheckAttribute : ActionFilterAttribute
{
    //Lazy<> is threadsafe and will call the Func in the constructor just once
    private static Lazy<bool> _configFileExists = new Lazy<bool>(ConfigFileExists);

    private static bool ConfigFileExists()
    {
        //Logic to check for config file here            
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!_configFileExists.Value)
        {
            //set your redirect here
            filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Setup", action = "Configure" }));
        }
    }
}

The last bit is to register the filter in your App_Start/FilterConfig.cs:

public static class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //...
        filters.Add(new ConfigFileCheckAttribute());
    }
}
Dean Goodman
  • 973
  • 9
  • 22
  • I am fully aware of base controller and filters, currently i implemented it with base controller, but i am looking a better solution. since response.redirect was available in asp.net form, i am looking for a solution equal to that for mvc. to me it's one unnecessary checking for every request that for high load application is a disaster – Mohammad Hossein Amri Aug 10 '16 at 21:33
  • 1
    Are you really worried that the few extra instructions to run a filter that checks the value of a static variable in memory is too much for your app? I daresay there are other areas that will affect the performance of your app (say, DB calls) orders of magnitude more than this. – Dean Goodman Aug 10 '16 at 21:38
0

If you need this feature for a specific page, use cookies like bellow inside the action:

public ActionResult Index()
{
    string cookieName = "NotFirstTime";
    if(this.ControllerContext.HttpContext.Request.Cookies.AllKeys.Contains(cookieName))
        // not first time 
        return View();
    else
    {
        // first time 
        // add a cookie.
        HttpCookie cookie = new HttpCookie(cookieName);
        cookie.Value = "anything you like: date etc.";
        this.ControllerContext.HttpContext.Response.Cookies.Add(cookie);
        // redirect to the page for first time visit.
        return View("FirstTime");
    }
}

If you want to use it for each action method, You need to write an ActionFilterAttribute to call it every time needed.

Note that the Request.Context is not longer available to the Application_Start event

Amirhossein Mehrvarzi
  • 18,024
  • 7
  • 45
  • 70