tags:

views:

396

answers:

3

What are the different or best ways I can implement a simple product dropdown in ASP.NET MVC, that redirect the user to a new product when they pick from the dropdown ?

I have currently have my primary mapping rule :

 routes.MapRoute(
              "Product",
              "Products/{sku}",
              new { controller = "Products", action = "ViewProduct", sku = "1001" }
           );

This allows me to enter a URL like this to view my product:

/Products/1002

I have a dropdown on my page which represents a list of products, and allows you to select form the list. (The sku parameter is the same name as the parameter in my controller's ViewProduct method) :

<% using (Html.BeginForm("ViewProduct", "Products"))
   { %>

    <%= Html.DropDownList("sku", ViewData.Model.ProductsList)%>
    <%= Html.SubmitButton() %>  <!-- just using a button now since its easier -->

<% } %>

This form targets the 'ViewProduct' command that I have in my controller (which is a full view that renders a product's information page).

The problem is that the HTML generated (when I view the HTML for /Products/1002) looks like this:

<form action="/Products/1002" method="post">

 <select id="sku" name="sku">
  <option value="1003">Product 1003</option>
  <option value="1002">Product 1002</option>
  <option value="2001">Product 2001</option>
 </select>

<input type="submit" />
</form>

It has gone and created the action link based upon the current URL (i guess it really had no choice). The problem is that when my controller is called after clicking 'Submit' it just goes ahead and fetches product 1002 again - and doesnt get overridden by the 'sku' parameter from the form.

Now, the actual value is available in Request.Params["sku"], but obvously its a pretty horrible practice to go and try to get it from there.

One extremely convoluted solution is as follows - create a different routing rule which hits a different controller method RedirectProduct.

routes.MapRoute(
           "Product Redirect",
           "Products/RedirectProduct",
           new { controller = "Products", action = "RedirectProduct" }
        );

Then the action to my form can simply be :

<% using (Html.BeginForm("RedirectProduct", "Products")) { %>

This hits my controller method, which has access to 'sku' (the correct one I want that is coming from the form params). The controller method just redirects to /Products/XXX .

public ActionResult RedirectProduct(string sku)
        {
            return new RedirectResult("/Products/" + sku);
        }

I don't like this solution at all - but I wanted to at least present what I had so far. I dont like adding extra mapping rules like this, and having to create different action names when I really just have one.

I'm sure theres 50 different ways I could do this -- hence me asking the question on stackoverflow -- to try and figure which is best. Unfortunately at this time the documentation for Forms handling for MVC that I've found is pretty simple.

PS. I'm not primarily looking for a JQuery solution (I want to master 'pure MVC' first) but I'd welcome one.

A: 

I think JavaScript is your only option here. The form tag can only submit to one URL, so if you want to avoid the RedirectProduct route (which I would agree, is awkward), you'll have to do javascript. Something like this (using jQuery) might do the trick:

<script type="text/javascript">
function doredirect() {
    window.location="http://www.mysite.com/Products/" + $('#sku').val
}
</script>
<% using (Html.BeginForm("ViewProduct", "Products"))
   { %>

    <%= Html.DropDownList("sku", ViewData.Model.ProductsList, new { onchange="doredirect" })%>
    <%-- Won't need this anymore: 'Html.SubmitButton()' --%>

<% } %>

(DISCLAIMER: I haven't fully tested that out, but if you run in to problems, I'll try to help in the comments for this answer)

Unfortunately, you lose the benefit of routing, since the URLs are generated on the client. If you change your routes, you'll have to manually update this code. One possible solution to that is to trick the routing system a little and generate a "template" URL on the server, to pass to your client code. Like this:

<script type="text/javascript">
var urlTemplate = <%= Url.Action("ViewProduct", "Products", new { sku="__sku__" }) %>
function doredirect() {
    window.location= urlTemplate.replace(/__sku__/,$('#sku').val)
}
</script>

(Same Disclaimer as above :P)

The server-side code should generate a URL like: http://mysite.com/Products/__sku__, then the javascript replaces the __sku__ token with the actual SKU.

anurse
something like this is probably how i actually WILL implement the dropdown. but lets pretend for a minute that this needs to work on a mobile app (which it doesnt) and i cant use JS...
Simon_Weaver
wow! this is turning out to be a lot more complex that I could have imagined. the mroe i'm learning about routing and URL generation (reverse routing) the tricker it seems to get. i've tried all kinds of things and almost got it working but everytime theres some small issue
Simon_Weaver
AFAIK, there's no way to do this with a drop-down list without either JavaScript or a Redirect. You could do it with a list of ActionLinks, but that would eliminate the purpose of the drop-down list :). Unfortunately, that's just the way HTML forms work :(
anurse
+1  A: 

The problem arises because both your Route uses {sku} and your form field is called sku. The {sku} from the route supersedes the sku form field (it would seem). A (fairly pragmatic) solution could be to have them named differently, so your controller action would be something like:

public void ActionResult ViewProduct(string routeSku, string sku)
{
    sku = sku ?? routeSku;
    // same as before
}

This would cause the "sku" parameter to be "null" in the initial get request (an routeSku will be whatever is in the URL), when using the submit button routeSku will be the same since you are posting back to the same URL but sku will be the form value. The logic will then select the sku value from the form if this was set.

veggerby
A: 

They fixed this for RC1! They officially classed it as a bug. Cool ( I think! )

• When a form posts to an action method that has a parameter name that matches a route parameter, the form value now overrides the route parameter. This fixes an issue in which a form that attempted to post a form field named id would not work because the default route has an id parameter.

Simon_Weaver