VSB
VSB

Reputation: 10375

Submit values from razor view to a controller action which accept values by HTTP Post method

I have a view in one of my controller which have list of items. When user clicks each item, browser must navigate to page which brings details about this item.

Controller for Details accepts values through post method and have complex input object as its input.

Here is sample method for to navigate to details page using GET method to send values:

function openDetailsPage(commodityID, commodityName) {

     dateFrom = convertNumbers2English('@(((FilterViewModel)ViewBag.ViewModel).dateValue_1)');
    dateTo = convertNumbers2English('@(((FilterViewModel)ViewBag.ViewModel).dateValue_2)');

    dateFrom = changeDateSeparator(dateFrom);
    dateTo = changeDateSeparator(dateTo);

    if (dateTo == null || dateTo == undefined)
        dateTo = "0";

    if (dateFrom == null || dateFrom == undefined)
        dateFrom = "0";
    @{
        string reportTypes = "0";
        if (((FilterViewModel)ViewBag.ViewModel).purchaseReportTypes != null)
        {
            reportTypes = String.Join(",", ((FilterViewModel)ViewBag.ViewModel).purchaseReportTypes);
        }
    }
    alert('@reportTypes');
    var url = '@Url.Action("ReportDetailed","BuyReport",new {
        commodityType =(((FilterViewModel)ViewBag.ViewModel).commodityType),
        commodityName="dummyCommodityName",
        department=((FilterViewModel)ViewBag.ViewModel).department,
        repository=((FilterViewModel)ViewBag.ViewModel).repository,
        commodity ="dummyCommodityID",
        purchaseReportTypes=reportTypes,
        dateValue_1="dummyDate1",
        dateValue_2="dummyDate2"
    })';
    alert(url);

    @*var url = '@Url.Action("ReportDetailed","BuyReport",
    new RouteValueDictionary
    {
        {"commodityType",((FilterViewModel)ViewBag.ViewModel).commodityType},
        {"commodityName","dummyCommodityName" },
        {"department",((FilterViewModel)ViewBag.ViewModel).department },
        {"repository",((FilterViewModel)ViewBag.ViewModel).repository },
        {"commodity","dummyCommodityID"},
        {"purchaseReportTypes",((FilterViewModel)ViewBag.ViewModel).purchaseReportTypes },
        {"dateValue_1",((FilterViewModel)ViewBag.ViewModel).dateValue_1 },
        { "dateValue_2",((FilterViewModel)ViewBag.ViewModel).dateValue_2 }

    })';*@
    url = url.replace("dummyCommodityID", commodityID);
    url = url.replace("dummyCommodityName", commodityName);
    url = url.replace("dummyDate1", dateFrom);
    url = url.replace("dummyDate2", dateTo);
    alert(url);
    openLink(url);
}

I have some difficulties with this type of routing for values:

  1. Input object is complex so route would be so complex. E.g. /BuyReport/ReportDetailed?commodityType=0&commodityName=dummyCommodityName&department=1&repository=2&commodity=dummyCommodityID&dateValue_1=dummyDate1&dateValue_2=dummyDate2 or /BuyReport/ReportDetailed/0/itemName/1/2/1/123/
  2. Any special characters in get parameters such as / will break routing
  3. I cannot pass stuff like arrays so I should convert them before sending

So I'm looking for a method to send parameters using 'Post' method like what form submit button does with below constraints:

  1. I have no forms in my view
  2. I want to post values to controller and page must navigate to details view
    1. Each item in first page, have different row and different ID so I think creating a form for each row is not reasonable.

I want to know are there any ways to implement Post parameters according to my requirements? I would not care if it would be a mixture of C#, JS and jQuery.

More Details:

Here is a sample row in my list page which calls openDetailsPage js function:

<a onclick="openDetailsPage(@item.CommodityId,'@Html.DisplayFor(modelItem => item.CommodityName)')">
    <div class="ios-content-box px-4 py-1 mb-3 ios-hover-box">
        <div class="row font-12 my-2 ios-divider-line">
            <div class="col-6 f-w-600 color-orange text-right">@Html.DisplayFor(modelItem => item.CommodityName)</div>
            <div class="col-6 text-left"> <i class="fas fa-chevron-left  fa-fw  color-orange "></i></div>
        </div>
        <div class="row font-12 my-2 ios-divider-line">
            <div class="col-6 text-gray-600 text-right">type</div>
            <div class="col-6 text-gray-600 text-left">@Html.DisplayFor(modelItem => item.TypesName)</div>
        </div>
        <div class="row font-12 my-2 ios-divider-line">
            <div class="col-6 text-gray-600 text-right">Code</div>
            <div class="col-6 text-gray-600 text-left">@Html.DisplayFor(modelItem => item.CommodityCode)</div>
        </div>
        <div class="row font-12 my-2 ios-divider-line">
            <div class="col-6 text-gray-600 text-right">Barcode</div>
            <div class="col-6 text-gray-600 text-left">@Html.DisplayFor(modelItem => item.CommodityBarcode)</div>
        </div>
        <div class="row font-12 my-2 ios-divider-line">
            <div class="col-6 text-gray-600 text-right">Unit Price</div>
            <div class="col-6 text-gray-600 text-left">@Html.DisplayFor(modelItem => item.UnitPrice)</div>
        </div>
        <div class="row font-12 my-2 ios-divider-line">
            <div class="col-6 text-gray-600 text-right">Total Price</div>
            <div class="col-6 text-gray-600 text-left">@Html.DisplayFor(modelItem => item.SumPrice)</div>
        </div>
    </div>
</a>

Currently my controller is as below:

[Route("BuyReport/ReportDetailed/{commodityType}/{commodityName}/{department}/{repository}/{commodity}/{purchaseReportTypes}/{dateValue_1}/{dateValue_2}")]

public async Task<ActionResult> ReportDetailed(
    string commodityType, 
    string commodityName,
    string department,
    string repository,
    string commodity,
    string purchaseReportTypes,
    string dateValue_1,
    string dateValue_2
    )
{


}

But I want to change it to something like this:

[HttpPost]
public async Task<ActionResult> ReportDetailed(DetailedViewModel detailedviewmodel){

    string commodity = detailedviewmodel.commodity;
    string commoditytype = detailedviewmodel.commoditytype;
    string department = detailedviewmodel.department;
    string purchasereporttypes = detailedviewmodel.purchasereporttypes;
    string repository = detailedviewmodel.repository;
    string startdate = detailedviewmodel.datevalue_1;
    string enddate = detailedviewmodel.datevalue_2;
    string commdoityname = detailedviewmodel.commodityname;

}

Where DetailedViewModel is defined as below:

public class DetailedViewModel
{
    public string commodityType { get; set; }
    public string commodityName { get; set; }
    public string department { get; set; }
    public string repository { get; set; }
    public string commodity { get; set; }
    public string[] purchaseReportTypes { get; set; }

    public string dateValue_1 { get; set; }//start date
    public string dateValue_2 { get; set; }//end date

}

Upvotes: 3

Views: 765

Answers (3)

Amirhossein Mehrvarzi
Amirhossein Mehrvarzi

Reputation: 18954

This is not the right way to meet your purpose. Your code looks vulnerable for exploiters too. Don't use solutions which break the normal web application behavior.

Instead, send the parameters to the corresponding controller method and then make an internal redirection with model passing (controller side). If your data is stored in database just send CommodityId and find details in controller side instead of sending entire details as form (HTTPPOST). In this way, you have a well designed project without unwanted crashes which come from breaking the behaviors and your code looks simple and clear as you want.

Upvotes: 6

Abdelrahman Gobarah
Abdelrahman Gobarah

Reputation: 1589

I agree with the solution by @A. Nadjar

one more note

Use HttpGet if you want user to share the url and show same data as he see it to another user ,

if not? use HttpPost with one Object parameter, because maybe there's a Nullable parameters the user won't search by so the url will be like this

BuyReport/ReportDetailed/dummyCommodityName/null/null/null/dummyCommodityID/null/2/0

or don't use this custom route [Route("BuyReport/ReportDetailed/{commodityType}/{commodityName}/{department}/{repository}/{commodity}/{purchaseReportTypes}/{dateValue_1}/{dateValue_2}")] so he can use the Query String and pass only the keys => values he want

Upvotes: 1

A. Nadjar
A. Nadjar

Reputation: 2543

One quick simple solution is to post via Ajax:

Let's imagine this as your controller:

    [HttpGet]
    public ActionResult ReportDetailed()
    {
        return View();
    }



    [HttpPost]
    public JsonResult ReportDetailed(DetailedViewModel detailedviewmodel)
    {
        var status = "error";
        var message = "";
        try
        {
            string commodity = detailedviewmodel.commodity;
            string commoditytype = detailedviewmodel.commodityType;
            string department = detailedviewmodel.department;
            List<string> purchasereporttypes = detailedviewmodel.purchaseReportTypes;
            string repository = detailedviewmodel.repository;
            string startdate = detailedviewmodel.dateValue_2;
            string enddate = detailedviewmodel.dateValue_1;
            string commdoityname = detailedviewmodel.commodityName;


            // your code here ...


            status = "success";                
            return Json(new { status, detailedviewmodel } , JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            message = ex.Message;
            return Json(new { status, message }, JsonRequestBehavior.AllowGet);
        }            
    }

Assuming you have defined DetailedViewModel inside the Models folder:

public class DetailedViewModel
{
    public string commodityType { get; set; }
    public string commodityName { get; set; }
    public string department { get; set; }
    public string repository { get; set; }
    public string commodity { get; set; }
    public List<string> purchaseReportTypes { get; set; }
    public string dateValue_1 { get; set; }//start date
    public string dateValue_2 { get; set; }//end date
}

In your View, I copy the whole Html and Javascript for you, just grab it and tweak it to your needs:

@model Your_Proj_Namespace.Models.DetailedViewModel

@{
    ViewBag.Title = "ReportDetailed";
}

<h2>ReportDetailed</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>DetailedViewModel</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.commodityType, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.commodityType, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.commodityType, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.commodityName, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.commodityName, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.commodityName, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.department, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.department, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.department, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <label>purchaseReportTypes: 3 inputs for example</label>
            <div class="col-md-10">
                <input type="text" name="purchaseReportTypes[0]" class="form-control inputPurchaseReportTypes " value="" />
                <input type="text" name="purchaseReportTypes[1]" class="form-control inputPurchaseReportTypes " value="" />
                <input type="text" name="purchaseReportTypes[2]" class="form-control inputPurchaseReportTypes " value="" />
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.repository, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.repository, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.repository, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.commodity, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.commodity, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.commodity, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.dateValue_1, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.dateValue_1, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.dateValue_1, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.dateValue_2, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.dateValue_2, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.dateValue_2, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" id="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}




@section scripts{
    <script>
        $('#submit').click(function (e) {
            e.preventDefault();
            alert('form submitted');


            var list = [];
            $('.inputPurchaseReportTypes').each( function (key, value) {
                list.push($(this).val());
            });



            const DATA = {
                commodityType: $('#commodityType').val(),
                commodityName: $('#commodityName').val(),
                department: $('#department').val(),
                repository: $('#repository').val(),
                commodity: $('#commodity').val(),
                purchaseReportTypes: list,
                dateValue_1: $('#dateValue_1').val(),
                dateValue_2: $('#dateValue_2').val(),
            };


            console.log(DATA);


            $.ajax({
                url: '/YourControllerName/ReportDetailed',
                type: "POST",
                contentType: "application/json",
                dataType: "json",
                data: JSON.stringify(DATA),

                success: function (result) {
                    alert('success');
                    console.log(result);
                    // your code here
                }
            });
        });



    </script>
}

If you prefer not to use Ajax, comment the javascript code above (all the code inside <script>), to post the form directly. I built and debugged the above code. Feel free to test it.

Hope this helped.

Finally, You might find the following links useful:

Upvotes: 3

Related Questions