简体   繁体   中英

How do I add route values to hyperlink?

Using VS2013, MVC5, assuming a model where there is a master/header record, and multiple related detail, the MVC view will display the header data in text boxes, and will display the detail in a web grid. The controller has an action method that accepts the id of the header record to display. There is no action method with a empty parameter list. When I click the resulting/displayed view's web grid header hyperlink I receive an error...

The parameters dictionary contains a null entry for parameter 'Id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Index(Int32)' in 'WebGridTest.Controllers.HomeController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter. Parameter name: parameters

Here is my code...

Code

Models

MyDetail

namespace WebGridTest.Models
{
    public class MyDetail
    {
        public string Column1 { get; set; }
        public string Column2 { get; set; }
        public string Column3 { get; set; }
    }
}

MyHeader

using System.Collections.Generic;

namespace WebGridTest.Models
{
    public class MyHeader
    {
        public MyHeader()
        {
            this.MyDetails = new List<MyDetail>();
        }

        public int Id { get; set; }
        public List<MyDetail> MyDetails { get; set; }
    }
}

Controllers

HomeController

using System.Web.Mvc;

namespace WebGridTest.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index(int Id)
        {
            var model = new Models.MyHeader();
            model.Id = 2; // HARD CODED VALUES FOR DEMONSTRATING THIS ISSUE

            var detail1 = new Models.MyDetail();
            detail1.Column1 = "1A";
            detail1.Column2 = "1B";
            detail1.Column3 = "1C";
            model.MyDetails.Add(detail1);

            var detail2 = new Models.MyDetail();
            detail2.Column1 = "2A";
            detail2.Column2 = "2B";
            detail2.Column3 = "2C";
            model.MyDetails.Add(detail2);

            var detail3 = new Models.MyDetail();
            detail3.Column1 = "3A";
            detail3.Column2 = "3B";
            detail3.Column3 = "3C";
            model.MyDetails.Add(detail3);

            return View(viewName: "Index", model: model);
        }
    }
}

Views

Index.cshtml

@model WebGridTest.Models.MyHeader

@{
    ViewBag.Title = "Home Page";
}

@using (Html.BeginForm(actionName: "Index", controllerName: "Home", method: FormMethod.Post))
{
    <p></p>
    @Html.TextBoxFor(x => x.Id)
    <p></p>
    WebGrid grid = new WebGrid(null, canPage: false);
    grid.Bind(Model.MyDetails);

    @grid.GetHtml(tableStyle: "table table-bordered lookup-table", columns:
        grid.Columns(
            grid.Column(
                "Column1"
                , "Column 1"
                , (item) => (item.Column1)
            )
            ,
            grid.Column(
                "Column2"
                , "Column 2"
                , (item) => (item.Column2)
            )
            ,
            grid.Column(
                "Column3"
                , "Column 3"
                , (item) => (item.Column3)
            )
        )
    )
}

when I run the project I receive the error described above. If I remove the parameter from action method 'Index' the program will not generate an error.

When I hover over the headers in the web page, I see the generated URL. I can see it does not include route values in the query string parameters list. It only includes sort values. Example...

http://localhost:62802/Home/Index?sort=Column1&sortdir=ASC

My example is contrived and trivial, but suffice to say, I need the Id to identify which records to display.

How can I specify the id route value on the column headers?

Upon research on the web, many developers suggest using JavaScript to modify the DOM to include the route values. That seems like it would work, but really moves away from MVC structures in favor of client-side evaluation and manipulation. The information is available in IIS/MVC, but the ability to apply the information (specifically to the web grid header anchor) is lacking.

I have found an alternative to JavaScript that keeps the solution entirely in C#. I will answer my own question as a Q&A style question.

The solution involves creating an extension method that will inject the route values into the resulting HTML prior to responding to the original client request. This entails adding the extension method class, and adding the method call in the view.

Example...

Extension Method Class

using System.Web;

namespace WebGridTest
{
    public static class ExtensionMethods
    {
        public static IHtmlString AddRouteValuesToWebGridHeaders(this IHtmlString grid, string routeValues)
        {
            var regularString = grid.ToString();
            regularString = regularString.Replace("?sort=", "?" + routeValues + "&sort=");

            HtmlString htmlString = new HtmlString(regularString);

            return htmlString;
        }
    }
}

Augmented View

(Notice the call to the extension method 'AddRouteValuesToWebGridHeaders')...

@model WebGridTest.Models.MyHeader

@{
    ViewBag.Title = "Home Page";
}

@using (Html.BeginForm(actionName: "Index", controllerName: "Home", method: FormMethod.Post))
{
    <p></p>
    @Html.TextBoxFor(x => x.Id)
    <p></p>
    WebGrid grid = new WebGrid(null, canPage: false);
    grid.Bind(Model.MyDetails);

    @grid.GetHtml(tableStyle: "table table-bordered lookup-table", columns:
        grid.Columns(
            grid.Column(
                "Column1"
                , "Column 1"
                , (item) => (item.Column1)
            )
            ,
            grid.Column(
                "Column2"
                , "Column 2"
                , (item) => (item.Column2)
            )
            ,
            grid.Column(
                "Column3"
                , "Column 3"
                , (item) => (item.Column3)
            )
        )
    ).AddRouteValuesToWebGridHeaders("Id=" + Model.Id.ToString())
}

Conclusion

Injecting into the HTML is not a preferred solution because like any other parsing algorithms it is highly assumptive. I have found no other way than HTML parsing. Perhaps downloading the Microsoft open-source version of this class and creating an override with the desired behavior would be a more elegant solution.

** Request ** - Microsoft - please add the ability to control/augment the anchor in the web grid header rendering.

Please feel free to comment...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM