Mohammad Hossein Amri
Mohammad Hossein Amri

Reputation: 2025

How to show a page only once in asp.net mvc

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.

Upvotes: 9

Views: 1855

Answers (3)

Amirhossein Mehrvarzi
Amirhossein Mehrvarzi

Reputation: 18974

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

Upvotes: 0

agriffin
agriffin

Reputation: 541

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.

Upvotes: 3

Dean Goodman
Dean Goodman

Reputation: 983

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());
    }
}

Upvotes: 0

Related Questions