javascript - How to Interact between a view component's form and a controller in ASP.NET Core? - Stack Overflow

admin2025-04-19  0

I'm beginner in web designing with ASP.NET Core. I wrote a view ponent that has a form with some inputs related to a view model. One of these inputs is a file input (of the IFormFile datatype).

I want to submit this view model to an action of a controller (POST action), check the validity of model, return another view ponent if the model state is valid, and remain on this view ponent with this view model if model state is not valid.

This is my View Model: PricingViewModel.cs

public class PricingViewModel
{
    [Display(Name = "Select a file")]
    public IFormFile formFile { get; set; }

    [Display(Name = "ColumnCode")]
    [Required(ErrorMessage = "Enter {0} value, please")]
    public string colCode { get; set; }

    [Display(Name = "ColumnName")]
    [Required(ErrorMessage = "Enter {0} value, please")]         
    public string colName { get; set; }
}   

My View Component (controller): PricingComponent.cs

public class PricingComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync(PricingViewModel pricing)
    {               
        return await Task.FromResult((IViewComponentResult)View("PricingView", pricing));
    }
}

My View Component (view): PricingView.cshtml

<form class="text-left" method="post" enctype="multipart/form-data">

   <input name="IsValidPricing" type="hidden" value="@ViewBag.isValid" />

   <div class="form-group text-left">
     <label asp-for="colCode" class="control-label"></label>
     <input asp-for="colCode" class="form-control" id="colCodeId"/>
     <span asp-validation-for="colCode" class="text-danger"></span>
   </div>

   <div class="form-group text-left">
     <label asp-for="colName" class="control-label"></label>
     <input asp-for="colName" class="form-control" id="colNameId"/>
     <span asp-validation-for="colName" class="text-danger"></span>
   </div>

   <div class="form-group text-left">
     <label asp-for="formFile " class="control-label"></label>
     <input type="file" accept=".xlsx, .csv" asp-for="formFile" id="MyInputFile"/>
   </div>

   <div class="form-group mt-4">
     <input type="submit" asp-action="ShowPricing" asp-controller="Home" value="Show" id="ShowPricingBtn" />
   </div>
</form>

My Home Controller: HomeController.cs

[HttpPost]
public IActionResult ShowPricing(PricingViewModel pricing)
{
    if (ModelState.IsValid)
    {
        int temp;
        if (!int.TryParse(pricing.colCode, out temp))
        {
            ViewBag.isValid = 0;
            ModelState.AddModelError("colCode", "Invalid Data");
            return ViewComponent("PricingComponent", new { pricing = pricing }); // 1
        }
        else if (!int.TryParse(pricing.colName, out temp))
        {
            ViewBag.isValid = 0;
            ModelState.AddModelError("colName", "Invalid Data");
            return ViewComponent("PricingComponent", new { pricing = pricing }); //2
        }
        else
        {
            ViewBag.isValid = 1;   
            
            // do something ...

            return ViewComponent("ShowPricingExcelComponent"); //Call another view ponent
        }
    }
    else
    {
       ViewBag.isValid = 0;
       return ViewComponent("PricingComponent", new { pricing = pricing }); //3
    }
}

Plan A

The above approach is my primary plan.

Problem

If I use options of submit input tag (asp-action, asp-controller) like above, the view model sends correctly, but I don't know how to handle the validity of the model and remain on this view ponent. In the above code, when the ShowPricing action runs, if the model state is valid, the code works correctly, but when model is invalid (1,2,3), the PricingView doesn't show the validation summery, and just loads with current view model.

Plan B

I used AJAX to send the viewModel to the action and instead of showing the validation summary, I send an alert to the user with AJAX. I changed PricingView as following:

My View Component (view): PricingView.cshtml

<form class="text-left" method="post" enctype="multipart/form-data">

   <input name="IsValidPricing" type="hidden" value="@ViewBag.isValid" />

   <div class="form-group text-left">
     <label asp-for="colCode" class="control-label"></label>
     <input asp-for="colCode" class="form-control" id="colCodeId"/>
     <span asp-validation-for="colCode" class="text-danger"></span>
   </div>

   <div class="form-group text-left">
     <label asp-for="colName" class="control-label"></label>
     <input asp-for="colName" class="form-control" id="colNameId"/>
     <span asp-validation-for="colName" class="text-danger"></span>
   </div>

   <div class="form-group text-left">
     <label asp-for="fromFile " class="control-label"></label>
     <input type="file" accept=".xlsx, .csv" asp-for="formFile" id="MyInputFile"/>
   </div>

    <script>
      $(document).ready(function () {
        $('#ShowPricingBtn').click(function () {
                var _url = '@Url.Action("ShowPricing", "Home")';
                var input = $("#MyInputFile").get(0).files[0]; 

                $.ajax({
                    type: "POST",
                    url: _url,
                    data: {
                       formFile: input,
                       colCode: $("#colCode").val(),
                       colName: $("#colName").val(),
                    },
                    success: function (result) 
                    {
                       var IsValid = $('body').find('[name="IsValidPricing"]').val();
                       if (IsValid) 
                       {
                          $("#ShowExcelTable").html(result);
                       }
                       else {
                          alert("Invalid Data");
                       }
                    },
                });
           });
       });
    </script>
   <div class="form-group mt-4">
     <input type="submit" value="Show" id="ShowPricingBtn" />
   </div>
</form>

Problem

In this code:

  1. If the model state is not valid, the alert sends correctly, but
  2. If the model state is valid, the formFile input doesn't send correctly to action and it's null in view model.

I don't know whether I should go with the original or the alternate approach these problems. Do you know where I'm going wrong?

I'm beginner in web designing with ASP.NET Core. I wrote a view ponent that has a form with some inputs related to a view model. One of these inputs is a file input (of the IFormFile datatype).

I want to submit this view model to an action of a controller (POST action), check the validity of model, return another view ponent if the model state is valid, and remain on this view ponent with this view model if model state is not valid.

This is my View Model: PricingViewModel.cs

public class PricingViewModel
{
    [Display(Name = "Select a file")]
    public IFormFile formFile { get; set; }

    [Display(Name = "ColumnCode")]
    [Required(ErrorMessage = "Enter {0} value, please")]
    public string colCode { get; set; }

    [Display(Name = "ColumnName")]
    [Required(ErrorMessage = "Enter {0} value, please")]         
    public string colName { get; set; }
}   

My View Component (controller): PricingComponent.cs

public class PricingComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync(PricingViewModel pricing)
    {               
        return await Task.FromResult((IViewComponentResult)View("PricingView", pricing));
    }
}

My View Component (view): PricingView.cshtml

<form class="text-left" method="post" enctype="multipart/form-data">

   <input name="IsValidPricing" type="hidden" value="@ViewBag.isValid" />

   <div class="form-group text-left">
     <label asp-for="colCode" class="control-label"></label>
     <input asp-for="colCode" class="form-control" id="colCodeId"/>
     <span asp-validation-for="colCode" class="text-danger"></span>
   </div>

   <div class="form-group text-left">
     <label asp-for="colName" class="control-label"></label>
     <input asp-for="colName" class="form-control" id="colNameId"/>
     <span asp-validation-for="colName" class="text-danger"></span>
   </div>

   <div class="form-group text-left">
     <label asp-for="formFile " class="control-label"></label>
     <input type="file" accept=".xlsx, .csv" asp-for="formFile" id="MyInputFile"/>
   </div>

   <div class="form-group mt-4">
     <input type="submit" asp-action="ShowPricing" asp-controller="Home" value="Show" id="ShowPricingBtn" />
   </div>
</form>

My Home Controller: HomeController.cs

[HttpPost]
public IActionResult ShowPricing(PricingViewModel pricing)
{
    if (ModelState.IsValid)
    {
        int temp;
        if (!int.TryParse(pricing.colCode, out temp))
        {
            ViewBag.isValid = 0;
            ModelState.AddModelError("colCode", "Invalid Data");
            return ViewComponent("PricingComponent", new { pricing = pricing }); // 1
        }
        else if (!int.TryParse(pricing.colName, out temp))
        {
            ViewBag.isValid = 0;
            ModelState.AddModelError("colName", "Invalid Data");
            return ViewComponent("PricingComponent", new { pricing = pricing }); //2
        }
        else
        {
            ViewBag.isValid = 1;   
            
            // do something ...

            return ViewComponent("ShowPricingExcelComponent"); //Call another view ponent
        }
    }
    else
    {
       ViewBag.isValid = 0;
       return ViewComponent("PricingComponent", new { pricing = pricing }); //3
    }
}

Plan A

The above approach is my primary plan.

Problem

If I use options of submit input tag (asp-action, asp-controller) like above, the view model sends correctly, but I don't know how to handle the validity of the model and remain on this view ponent. In the above code, when the ShowPricing action runs, if the model state is valid, the code works correctly, but when model is invalid (1,2,3), the PricingView doesn't show the validation summery, and just loads with current view model.

Plan B

I used AJAX to send the viewModel to the action and instead of showing the validation summary, I send an alert to the user with AJAX. I changed PricingView as following:

My View Component (view): PricingView.cshtml

<form class="text-left" method="post" enctype="multipart/form-data">

   <input name="IsValidPricing" type="hidden" value="@ViewBag.isValid" />

   <div class="form-group text-left">
     <label asp-for="colCode" class="control-label"></label>
     <input asp-for="colCode" class="form-control" id="colCodeId"/>
     <span asp-validation-for="colCode" class="text-danger"></span>
   </div>

   <div class="form-group text-left">
     <label asp-for="colName" class="control-label"></label>
     <input asp-for="colName" class="form-control" id="colNameId"/>
     <span asp-validation-for="colName" class="text-danger"></span>
   </div>

   <div class="form-group text-left">
     <label asp-for="fromFile " class="control-label"></label>
     <input type="file" accept=".xlsx, .csv" asp-for="formFile" id="MyInputFile"/>
   </div>

    <script>
      $(document).ready(function () {
        $('#ShowPricingBtn').click(function () {
                var _url = '@Url.Action("ShowPricing", "Home")';
                var input = $("#MyInputFile").get(0).files[0]; 

                $.ajax({
                    type: "POST",
                    url: _url,
                    data: {
                       formFile: input,
                       colCode: $("#colCode").val(),
                       colName: $("#colName").val(),
                    },
                    success: function (result) 
                    {
                       var IsValid = $('body').find('[name="IsValidPricing"]').val();
                       if (IsValid) 
                       {
                          $("#ShowExcelTable").html(result);
                       }
                       else {
                          alert("Invalid Data");
                       }
                    },
                });
           });
       });
    </script>
   <div class="form-group mt-4">
     <input type="submit" value="Show" id="ShowPricingBtn" />
   </div>
</form>

Problem

In this code:

  1. If the model state is not valid, the alert sends correctly, but
  2. If the model state is valid, the formFile input doesn't send correctly to action and it's null in view model.

I don't know whether I should go with the original or the alternate approach these problems. Do you know where I'm going wrong?

Share edited Jul 13, 2021 at 19:21 Jeremy Caney 7,702106 gold badges55 silver badges83 bronze badges asked Jan 19, 2021 at 5:29 NedaMNedaM 1331 gold badge3 silver badges12 bronze badges 6
  • 1 I've checked your code and it's working with validation. Errors are displayed. Why you are using return ViewComponent(...) instead of return View(...)? – Just the benno Commented Jan 19, 2021 at 6:53
  • @Justthebenno Which plan did you check? How did you write code that is worked and validation displayed. I used return viewComponent(...) because my view is a viewponent (in ~/Views/shared/Components). – NedaM Commented Jan 19, 2021 at 6:59
  • 1 I made some changes like always returning the same ViewComponent return ViewComponent("PricingComponent", new { pricing = pricing }); I wanted to avoid having new ponents for each case. I build a simple GET method like ``` [HttpGet] public IActionResult ShowPricing() => ViewComponent("PricingComponent", new { pricing = new PricingViewModel() });``` and send the form. Result: pasteboard.co/JKkcYTO.png – Just the benno Commented Jan 19, 2021 at 7:05
  • 1 For your use-case, a simple view seems to be enough. ViewComponents are more for advanced scenarios – Just the benno Commented Jan 19, 2021 at 7:08
  • @Justthebenno I don't write all of my project. I just write a part of this. because of rest of my project, I have to use view ponent, not simple view. – NedaM Commented Jan 19, 2021 at 7:23
 |  Show 1 more ment

2 Answers 2

Reset to default 5

Not sure how do you call view ponents,here are the working demos:

For PlanA

1.Create ViewComponents/PricingComponent.cs and ViewComponents/ShowPricingExcelComponent.cs.

public class PricingComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync(PricingViewModel pricing)
    {
        return await Task.FromResult((IViewComponentResult)View("PricingView", pricing));
    }
}    
public class ShowPricingExcelComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync(PricingViewModel pricing)
    {
        return await Task.FromResult((IViewComponentResult)View("ShowPricingExcel", pricing));
    }
}

2.Create Views/Shared/Components/PricingComponent/PricingView.cshtml.

@model PricingViewModel 
<form class="text-left" method="post" enctype="multipart/form-data">

    <input name="IsValidPricing" type="hidden" value="@ViewBag.isValid" />

    <div class="form-group text-left">
        <label asp-for="colCode" class="control-label"></label>
        <input asp-for="colCode" class="form-control" id="colCodeId" />
        <span asp-validation-for="colCode" class="text-danger"></span>
    </div>

    <div class="form-group text-left">
        <label asp-for="colName" class="control-label"></label>
        <input asp-for="colName" class="form-control" id="colNameId" />
        <span asp-validation-for="colName" class="text-danger"></span>
    </div>

    <div class="form-group text-left">
        <label asp-for="formFile " class="control-label"></label>
        <input type="file" accept=".xlsx, .csv" asp-for="formFile" id="MyInputFile" />
    </div>

    <div class="form-group mt-4">
        <input type="submit" asp-action="ShowPricing" asp-controller="Home" value="Show" id="ShowPricingBtn" />
    </div>
</form>

3.Create Views/Shared/Components/ShowPricingExcelComponent/ShowPricingExcel.cshtml.

<h1>Excel....</h1>

Project Structure:

4.Views/Home/Index.cshtml:

@await Component.InvokeAsync("PricingComponent")

5.HomeController:

public class HomeController : Controller
{       
    public IActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public IActionResult ShowPricing(PricingViewModel pricing)
    {
        if (ModelState.IsValid)
        {
            int temp;
            if (!int.TryParse(pricing.colCode, out temp))
            {
                ViewBag.isValid = 0;
                ModelState.AddModelError("colCode", "Invalid Data");
                return View("Index", pricing);
            }
            if (!int.TryParse(pricing.colName, out temp))
            {
                ViewBag.isValid = 0;
                ModelState.AddModelError("colName", "Invalid Data");
                return View("Index", pricing);
            }
            else 
            {
                ViewBag.isValid = 1;

                // do something ...

                return ViewComponent("ShowPricingExcelComponent"); //Call another view ponent
            }              
        }
        else
        {
            ViewBag.isValid = 0;
            return View("Index", pricing); //3
        }
    }
}

Result:

For PlanB

1.Create ViewComponents/PricingComponent.cs and ViewComponents/ShowPricingExcelComponent.cs.

2.Create Views/Shared/Components/PricingComponent/PricingView.cshtml.

Firstly,it should be type="button" otherwise it will call twice to the backend.Secondly,what you did in ajax is not correct,more detailed explation you could refer to this answer.At last,you could not judge the modelstate by get the value of IsValidPricing value in your sucess function.Because the value you get is always be the data you first render the page,you cannot get the changed ViewBag value when ajax post back.

@model PricingViewModel
<form class="text-left" method="post" enctype="multipart/form-data">

    <input name="IsValidPricing" type="hidden" value="@ViewBag.isValid" />

    <div class="form-group text-left">
        <label asp-for="colCode" class="control-label"></label>
        <input asp-for="colCode" class="form-control" id="colCodeId" />
        <span asp-validation-for="colCode" class="text-danger"></span>
    </div>

    <div class="form-group text-left">
        <label asp-for="colName" class="control-label"></label>
        <input asp-for="colName" class="form-control" id="colNameId" />
        <span asp-validation-for="colName" class="text-danger"></span>
    </div>

    <div class="form-group text-left">
        <label asp-for="formFile " class="control-label"></label>
        <input type="file" accept=".xlsx, .csv" asp-for="formFile" id="MyInputFile" />
    </div>

    <div class="form-group mt-4">
        @*it should be type="button"*@
        <input type="button" value="Show" id="ShowPricingBtn" />
    </div>
</form>

<script src="~/lib/jquery/dist/jquery.min.js"></script>

<script>
      $(document).ready(function () {
        $('#ShowPricingBtn').click(function () {
            var _url = '@Url.Action("ShowPricing", "Home")';
            var input = $("#MyInputFile").get(0).files[0];

            var fdata = new FormData();
            fdata.append("formFile", input);
            $("form input[type='text']").each(function (x, y) {
                fdata.append($(y).attr("name"), $(y).val());
            });
            $.ajax({
                type: "POST",
                url: _url,
                data: fdata,
                contentType: false,   
                processData: false,
                success: function (result)
                {
                    console.log(result);
                    if (result==false)
                    {
                        alert("Invalid Data");
                    }
                    else {
                        $("#ShowExcelTable").html(result);

                    }
                },
            });
        });
  });
</script>

3.Create Views/Shared/Components/ShowPricingExcelComponent/ShowPricingExcel.cshtml.

<h1>Excel....</h1>

4.Views/Home/Index.cshtml:

@await Component.InvokeAsync("PricingComponent")
<div id="ShowExcelTable"></div>

5.HomeController:

public class HomeController : Controller
{       
    public IActionResult Index()
    {
        return View();
    }
    [HttpPost]
    public IActionResult ShowPricing(PricingViewModel pricing)
    {
        if (ModelState.IsValid)
        {
            int temp;
            if (!int.TryParse(pricing.colCode, out temp)|| !int.TryParse(pricing.colName, out temp))
            {
                ViewBag.isValid = 0;
                return Json(false);
            }
            else
            {
                ViewBag.isValid = 1;

                // do something ...
                return ViewComponent("ShowPricingExcelComponent"); //Call another view ponent
            }
        }
        else
        {
            ViewBag.isValid = 0;
            return Json(false);
        }
    }
}

Result:

I'm not able to reproduce your error. Your code, as presented, works as expected. A validation message is displayed.

To make it a working example, I've added a GET method first.

[HttpGet]
public IActionResult ShowPricing() => ViewComponent("PricingComponent", new { pricing = new PricingViewModel() });

Open the URL Home/ShowPricing

Fill out the form.

Send the form. And the validation message is displayed.

转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1745072651a283379.html

最新回复(0)