ASP.NET MVC 5 Attribute Routing: Url.Action returns null
Asked Answered
A

2

6

I am experiencing an issue with a refactoring of our payment processing action method (called by our 3rd-party online payment provider). We have a product controller with the [Authorize] and [RoutePrefix("products")] attributes at the class level, and action methods including the following:

  • Product(string contractNumber) with routing attribute [Route("{productCode}")]
  • MakePayment(string productCode, PaymentAmountType? amountSelection, decimal? amountValue) with routing attribute [Route("{productCode}")] and [HttpPost] attribute
  • ProcessPayment(string productCode, string result) with routing attribute [Route("{productCode}")]

Because our payment gateway needs to be able to call our ProcessPayment action before the visitor is redirected to that same URL, we've had to refactor that to a separate controller without the [Authorize] attribute. (We already have mechanisms to prevent double-crediting a payment.)

Before this refactoring, the MakePayment action method correctly formulated the correct return URL in the following call to Url.Action():

var rawCallbackUrl = Url.Action("ProcessPayment", new { productCode = productCode });

The ProcessPayment action method has now been moved out of the product controller and into a new controller, ExternalCallbackController, which has no attributes (let alone [Authorize]), in order to avoid having an HTTP 401 response returned to the payment provider.

The route attribute on ProcessPayment is now [Route("order-processing/{productCode}/process-payment")] to avoid clashing with the RoutePrefix on the product controller. All references to this updated action method are updated to specify the ExternalCallbackController.

Manually browsing to the URL causes the breakpoint set inside ProcessPayment to be hit, so the route apparently works successfully.

The problem is that in MakePayment, the following call returns null:

var rawCallbackUrl = Url.Action("ProcessPayment", "ExternalCallback", new { productCode = productCode });

Given that I am specifying both the controller and action method, why is Url.Action(...) not returning the expected URL in the form order-processing/{productCode}/process-payment?

From Day 1, our RegisterRoutes() method in RouteConfig has had attribute routing properly initialised with

routes.MapMvcAttributeRoutes();

How can I get the correct URL returned from the call to Url.Action(...)?

Anorthic answered 1/3, 2015 at 23:39 Comment(0)
A
5

Doh - I've figured out what went wrong. Despite the sanitising of names in the source code (which were specific to our client), it turns out that there was a mismatch in the following call:

var rawCallbackUrl = Url.Action("ProcessPayment", "ExternalCallback", new { productCode = productCode });

and the ProcessPayment() action method.

This is akin to the following (note the use of productNumber instead of productCode):

var rawCallbackUrl = Url.Action("ProcessPayment", "ExternalCallback", new { productNumber = productNumber });

trying to reference the action method:

[Route("order-processing/{productCode}/process-payment")]
public ActionResult ProcessPayment(string productCode, string result)
{
    ...
}

It also turns out that I can also use the same prefix "products" instead of "order-processing", as MVC creates one Route per attribute route in the routing table. Hope that helps others stuck in a similar situation.

Anorthic answered 2/3, 2015 at 1:17 Comment(0)
L
0

I got this Error : Url.Action return null.

public async Task<IActionResult> ForgotPassword(ForgotPassword forgotPassword)
    {
        if (ModelState.IsValid)
        { 
            // Find the user by email
            var user = await userManager.FindByEmailAsync(forgotPassword.Email);
            // If the user is found AND Email is confirmed
            if (user != null && await userManager.IsEmailConfirmedAsync(user))
            {
                // Generate the reset password token
                var token = await userManager.GeneratePasswordResetTokenAsync(user);
                // Build the password reset link
                var passwordResetLink = Url.Action("ResetPassword", "Account",
                        new { email = forgotPassword.Email, token }, Request.Scheme);
                ViewBag.PRL = passwordResetLink;

                // Send the user to Forgot Password Confirmation view
                return View("ForgotPasswordConfirmation");
            }
            return View("ForgotPasswordConfirmation");
        }

        return View(forgotPassword);
    }
Lemoine answered 21/11, 2020 at 2:44 Comment(1)
Please add more details to your description it helps other users to dig more into your use. Add which version, technology and IDE you using or use tagging so that your question will get attention from proper queues.Happiness

© 2022 - 2024 — McMap. All rights reserved.