Search code examples
c#asp.net-coreauthorization

How can i ensure that controller methods are entered only in a specific sequence


Controller:

public async Task<IActionResult> Cart()
{
    //some logic
}

public async Task<IActionResult> Payment()
{
    //some logic
}

Cart.cshtml:

<form method="post" asp-action="Payment" asp-controller="Tech">
    <button></button>
</form>

I want to prevent users from just typing MyDomain/MyController/Payment and accessing the payment page. I want to ensure that the user has clicked on the button on the cart page to be allowed to enter the payment page. A solution I thought of is using TempData[] to save the name of the last visited page and then check if the dictionary is null or it has the name, but I am sure there's gotta be a better way.


Solution

  • How can i ensure that controller methods are entered only in a specific sequence

    Well, according to your scenario and descripion it can be handled using couple of ways apart from TempData[]. Also here tempData is not ideal or best way to do that, because tempdata is non persistent.

    The ideal ways you could consider either Model Bindings or Using session state. Using both way, first we would check user last visited page status and if the users coming from cart page we will allow them to visit payment page. Other wise redirect them to the cart page again.

    Let's have a look in practice, how we could achieve that,

    Using Model Binding:

    This is one of the simple way to implement your requirement. We would set the page status while loading the cart page as a hidden value and check the value while load the payment page if the value is true or not, means users visiting the page directly or from cart.

    Let's implement that:

    Demo Model:

    public class TransitionModel
    {
        public bool FromCart { get; set; }
    }
    

    Controller:

    public class TechController : Controller
    {
        public IActionResult Cart()
        {
            var model = new TransitionModel();
            return View(model);
        }
    
        //[HttpPost]
        public async Task<IActionResult> Payment(TransitionModel model)
        {
            if (model.FromCart)
            {
                
                return View();
            }
            else
            {
                
                return RedirectToAction("Cart");
            }
        }
    }
    

    View:

    @model TransitionModel
    
    <form method="get" asp-action="Payment" asp-controller="Tech">
        <input type="hidden" asp-for="FromCart" value="true" />
        <button type="submit">Go to Payment</button>
    </form>
    

    Note: For loading payment page you probably has one page for loading view which is a type of GET and another action should have with POST method which would receive payment request, in both action you could filter FromCart value. I am only checking for loading so I used GET. You can try both.

    Output:

    enter image description here

    enter image description here

    enter image description here

    Middleware with session Approach:

    In order to use Middleware and session state approach, first of all we need session variable where we would keep tract of last visited page. In this case if the last visited page is cart we would allow user to visit payment anything apart from this we would ignore.

    Let's check the implementation:

    public class NavigationMiddleware
    {
        private readonly RequestDelegate _next;
    
        public NavigationMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        public async Task InvokeAsync(HttpContext context)
        {
            var path = context.Request.Path.Value.ToLower();
            
            if (!path.Contains("/payment") && !path.Contains("/cart"))
            {
               
                await _next(context);
                return;
            }
    
          
            context.Session.SetString("LastVisitedPage", path);
    
            await _next(context);
        }
    }
    

    Program.cs:

    app.UseSession();
    app.UseMiddleware<NavigationMiddleware>();
    

    Note: As you can see, we are storing the current page in session and Ignoring paths that are not part of the sequence. You could refer to this official dcoument for middleware and session state management.