Reputation: 133
I'm beginner in web designing with ASP.NET Core. I wrote a view component 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 component if the model state is valid, and remain on this view component 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 component
}
}
else
{
ViewBag.isValid = 0;
return ViewComponent("PricingComponent", new { pricing = pricing }); //3
}
}
The above approach is my primary plan.
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 component. 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.
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>
In this code:
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?
Upvotes: 4
Views: 3681
Reputation: 36715
Not sure how do you call view components,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 component
}
}
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 component
}
}
else
{
ViewBag.isValid = 0;
return Json(false);
}
}
}
Upvotes: 5
Reputation: 2601
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.
Upvotes: 1