1

I've implemented a throttling mechanism on my Login-action following this post. It works the way I want it to, except one thing. Whenever the mechanism is returning my message, I get redirected to a new, blank view with my message in it.

Is it possible, and if so how, to return this message back to my login-controller / view so it can be displayed in my _LoginPage.cshtml?

Here's my attribute:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ThrottleAttribute : ActionFilterAttribute
{
    public string Name { get; set; }
    public int Seconds { get; set; }
    public string Message { get; set; }
    public int AllowedRetries { get; set; }
    private int _loginAttempts = 1;

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        string key = string.Concat(Name, "-", filterContext.HttpContext.Request.UserHostAddress);
        bool allowExecute = false;
        _loginAttempts++;

        while (_loginAttempts <= AllowedRetries)
        {
            return;
        }

        if (HttpRuntime.Cache[key] == null)
        {
            HttpRuntime.Cache.Add(key,
                true, 
                null, 
                DateTime.Now.AddSeconds(Seconds),
                Cache.NoSlidingExpiration,
                CacheItemPriority.Low,
                null); 

            allowExecute = true;
        }

        if (!allowExecute)
        {
            if (String.IsNullOrEmpty(Message))
            {
                Message = "AllowedRetries Exceeded. You have to wait {n} seconds.";
            }

            filterContext.Result = new ContentResult
            {
                // TODO: Redirect message text to login-view
                Content = Message.Replace("{n}" , Seconds.ToString())
            }; 

            filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
        }
    }
}

LoginController:

[Throttle(Name = "LoginThrottle", Seconds = 10, AllowedRetries = 3)]
[HttpPost]
public ActionResult Index(LoginViewModel model)
{
    ...login logic...

    return View(model);
}

UPDATE

Following @lin's suggestion I got the information I wanted, but I'm not able to display it in my view. In my cshtml file I start by setting a variable before referencing it further down in my markup:

@{
    var informationToUser = ViewBag.Information ?? "";
}
<div class="panel panel-primary>
    ...
    <p>@informationToUser</p>
</div>

Still, nothing happens :( As mentioned in comments below, my url now looks like this: http://localhost:54508/?information=AllowedRetries%20Exceeded.%20You%20have%20to%20wait%2010%20seconds.

Community
  • 1
  • 1
Nicklas Pouey-Winger
  • 3,023
  • 6
  • 43
  • 75

2 Answers2

2

I'm not sure this is what you want. But it should work. See below code:

Throttle Attribute

  if (!allowExecute)
    {
        if (String.IsNullOrEmpty(Message))
        {     
            Message = "AllowedRetries Exceeded. You have to wait {n} seconds.";
        }
        filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;

        var values = new RouteValueDictionary(new
        {
                action = "Login",
                controller = "Account",
                exceptiontext = Message.Replace("{n}", Seconds.ToString())
        });
        filterContext.Result = new RedirectToRouteResult(values);
    }

Login Action Method

  [HttpGet]
  public ActionResult Login(string returnUrl, string exceptiontext)
    {
        ViewBag.ReturnUrl = returnUrl;
        ViewBag.Exceptiontext = exceptiontext;
        return View();
    }

Update: You don't need to validate if the ViewBag is null.

Login View

 //
<div class="panel panel-primary>
    <p>@ViewBag.Exceptiontext </p>
</div>
//

The URL should like :

http://localhost:58929/Account/Login?exceptiontext=AllowedRetries%20Exceeded.%20You%20have%20to%20wait%2010%20seconds.
Lin
  • 15,078
  • 4
  • 47
  • 49
  • Thanks for your suggestion! I've set a breakpoint in both LoginController.cs and ThrottleAttribute.cs. So, it triggers after x amount of failed login attempts and it fills out the values object properly. However, it does not seem to return back to my Index action in LoginController.cs when doing `filterContext.Result = new RedirectToRouteResult(values);` Suggestions? – Nicklas Pouey-Winger Dec 19 '13 at 15:38
  • Disregard that. I noticed that my url is containing the following: `http://localhost:54508/?information=AllowedRetries%20Exceeded.%20You%20have%20to%20wait%2010%20seconds.#`. Now, I guess it's just a matter of updating my view somehow. Doing `@ViewBag.information` in my view displays nothing :( – Nicklas Pouey-Winger Dec 19 '13 at 15:43
  • hi @NicklasWinger, I guess you should check how did you display the information using the ViewBag.information, because it works for me. – Lin Dec 19 '13 at 15:52
  • I got why you have the problem. Your URL is wrong. See my update. – Lin Dec 19 '13 at 16:17
  • Url was right. Turned out some other developer had implemented a similar Index action further down in the controller. Got it working now! – Nicklas Pouey-Winger Dec 19 '13 at 16:21
0

I developed an OSS project called MvcFlash that does exactly what you want to do and handles a lot of what you want. You can install it through NuGet and it is easy to get started with.

PM > Install-Package MvcFlash.Web

Using it in code is as simple as this.

// In your attribute
Flash.Error("Ooops", "There was an exception", id: "Exception")

In your view:

@Html.Flash()

The reason you need the id in the attribute is because the attribute might execute multiple times and thus you would see the message multiple times when you call flash. Setting the id makes the message unique.

Here is some more information.

https://github.com/khalidabuhakmeh/MvcFlash2

Khalid Abuhakmeh
  • 10,709
  • 10
  • 52
  • 75