How to update List of string - c#

I'm doing a course on PluralSight but the code in the course is not complete and there is a quickedit IActionResult that I can't seem to figure out.
In my View I have the code below:
#model IList<string>
#if (Model.Count > 0)
{
<form asp-action="QuickEdit" method="post">
#for (var i = 0; i < Model.Count; i++)
{
<div class="form-group">
<label>Soup name #(i + 1);</label>
<input id="soupNames" name="soupNames" asp-for="#Model[i]" class="form-control"/>
</div>
}
<button type="submit" class="btn btn-primary">Update</button>
</form>
}
else
{
<h2>No Soups in the system</h2>
}
The Controller:
public IActionResult QuickEdit()
{
var soupNames = _soupRepository.AllSoups.Select(s => s.SoupName).ToList();
return View(soupNames);
}
[HttpPost]
public IActionResult QuickEdit(List<string> soups)
{
var soupNames = _soupRepository.AllSoups.Select(s => s.SoupName).ToList();
for (var i = 0; i < soupNames.Count; i++)
{
soupNames[i] = soups[i];
}
return View(soups);
}
Could someone help me out?
I want the values passed through replace the original values when I click the update button.

this line
<input id="soupNames" name="soupNames" asp-for="#Model[i]" class="form-control"/>
need to change to something like
<input id="#(i)_soups" name="[#(i)].soups" asp-for="#Model[i]" class="form-control"/>
you need to google model binding lists
try , it may show you the raw syntax so you can change to just input
#Html.TextBoxFor(m => #Model[i] )

Related

How to submit multiple identical forms with one button

I'm currently building and application in ASP.NET Core MVC and I have ran into a problem which I cannot solve.
I have a form for something and that form should contain multiple identical fields which are added dynamically (1-10). I have managed to do that by creating a ViewComponent which contains those form fields and I make an Ajax call to invoke the view component into a tab if a user chooses to add another segment of those fields.
function CallViewComponent(num_tabs) {
var data = { id: num_tabs };
$.ajax({
type: 'POST',
url: '/Create/CreateActivityForm',
cache: false,
data: data
}).done(function (result) {
var container = "#activity-" + num_tabs;
$(container).html(result);
});
}
The problem arises because each of those fields in that view component shares a name with the other fields so each time I invoke another view component the radio buttons are shared between all identical fields.
Here is a snippet of the ViewComponent:
#model CreateActivityViewModel
<div class="activity-tab">
<div class="form-group">
<label asp-for="La.OrdinalNumber">Redni broj aktivnosti</label><br />
<select asp-for="La.OrdinalNumber" class="ordinals" style="width:50%">
#foreach (var on in Model.OrdinalNumbers)
{
<option value="#on.Value">#on.Text</option>
}
</select>
</div>
<div class="form-group">
<label asp-for="La.Latype">Tip aktivnosti</label><br />
<select asp-for="La.Latype" class="activity-type" style="width:50%">
#foreach (var lt in Model.LaTypes)
{
<option value="#lt">#lt.LatypeName</option>
}
</select>
</div>
<div class="form-group">
<label asp-for="La.Laname">Naziv aktivnosti</label>
<input asp-for="La.Laname" type="text" name="La.Laname" placeholder="Unesite naziv aktivnosti" class="f1-activity-name form-control" id="f1-activity-name">
</div>
Here is my controller which returns the ViewComponent:
[HttpPost]
public IActionResult CreateActivityForm(int id)
{
return ViewComponent("ActivityTab", id);
}
Here is the Invoke method from the ViewComponent:
public IViewComponentResult Invoke(int id)
{
var latypes = _laTypeRepository.GetAllLaType.ToList();
var ordinals = new List<SelectListItem>();
var laperformances = _laPerformanceRepository.GetAllLaPerformance.ToList();
var teachingAids = _teachingAidRepository.GetAllTeachingAid.ToList();
var strategyMethods = _strategyMethodRepository.GetAllStrategyMethod.ToList();
var laCollaboration = _laCollaborationRepository.GetAllLaCollaboration.ToList();
for (int i = 1; i <= 100; i++)
{
ordinals.Add(new SelectListItem($"{ i }. aktivnost", i.ToString()));
}
return View( new CreateActivityViewModel
{
FormId = id,
LaTypes = latypes,
OrdinalNumbers = ordinals,
LaPerformances = laperformances,
StrategyMethods = strategyMethods,
Lacollaborations = laCollaboration,
TeachingAids = teachingAids,
TeachingAidUser = new List<TeachingAid>(),
TeachingAidStudent = new List<TeachingAid>()
});
}
And finally this is where the ViewComponent gets invoked. It is inside another form because I need to submit the main form and all the ViewComponents at once:
<fieldset>
<h4>Aktivnosti</h4>
<!-- Activity Tabs -->
<div id='activity-tabs'>
<!-- Activity Links -->
<ol id="#activity-links">
<li><a href='#activity-1'>#1</a></li>
<li id="add-activity"><button type="button" id='add-activity'><i class="fa fa-plus"></i></button></li>
</ol>
<!-- Activity Content -->
<div id='activity-1'>
<h3>Aktivnost #1</h3>
#await Component.InvokeAsync("ActivityTab")
</div>
</div>
<!-- Navigation Buttons -->
<div class="f1-buttons">
<button type="button" class="btn btn-previous">Prethodna</button>
<button type="submit" class="btn btn-submit">Kreiraj scenarij</button>
</div>
</fieldset>
My question is how do I separate those identical forms and be able to submit them and store every single one of those forms into an array of objects which I can then store into a database.
I am open to all ideas and will change the entire code if necessary.
Thank you!
If you have an array of objects you need to render the components using a FOR loop rather than a FOR-EACH. I like to push common code into a shared view but you can code direct in the view. You will need to set your asp-for attributes in order to bind values to the model
#for (int index = 0; index < Model.Resources.Count; index++)
{
<partial name="_ResourceHidden" for="#Model.Resources[index]" />
Direct render
#for (int index = 0; index < Model.Resources.Count; index++)
{
<tr>
<td>
#Model.Resources[index].ResourceName
</td>

Passing data from partial view to parent view

I am trying to pass a value from partial view to parent view. I tried the below which didn't worked for me. Can some one help me out how can I achieve this? By using the value that partial view returned, I am trying to hide a button which is in the parent view.
Parent View:
<div id="ProductCount">
#{
Html.RenderAction("ProductPartialView", "ProductList", new { area = "PR", ProductID = Model.ProductID, allowSelect = true});
}
<div class="saveBtnArea">
<input type="submit" value="Submit App" id="btn-submit" />
</div>
jQuery(document).ready(function () {
var ProductCount = jQuery("#ProductCount").val();
if (ProductCount == 0) {
jQuery("#btn-submit").hide();
}
else {
jQuery("#btn-submit").show();
}
});
Partial View:
<fieldset>
<div>
<input type="hidden" id="ProductCount" value="5" />
</div>
</fieldset>
You can implement change event for hidden input field like this
$('#ProductCount').change(function(){
var ProductCount = $(this).val();
if (ProductCount == 0) {
jQuery("#btn-submit").hide();
}
else {
jQuery("#btn-submit").show();
}
}).trigger('change');
$('#ProductCount').val(5);
$('#ProductCount').change(function(){
var ProductCount = $(this).val();
if (ProductCount == 0) {
jQuery("#btn-submit").hide();
}
else {
jQuery("#btn-submit").show();
}
}).trigger('change');
$('#ProductCount').val(5);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type='hidden' id='ProductCount' />
<button id='btn-submit'>Submit</button>

Calling methods of #functions on Razor issue

this is my first post on Stackoverflow so I apologize for any errors on this post (And sorry for my English too).
I'm having a problem using C# functions on Razor. I have a form with some textboxes to fill them with data from a SQL Server database. I also have some buttons to move across the elements on the DB table (like previous, next, first...).
I'll show you the code:
#{ var codcur = "";
var descripcion = "";
var horas = "";
var tutor = "";
int pos = 0;
if(IsPost) {
pos = Convert.ToInt32(Session["position"]);
}
else {
pos = 0;
}
using(PracticaRazor2.ModeloOcupacional Contexto = new PracticaRazor2.ModeloOcupacional()) {
var registro = (from cur in Contexto.CURSOS orderby cur.COD_CUR select cur).Skip(pos).First();
codcur = registro.COD_CUR;
descripcion = registro.DESCRIPCION;
horas = registro.HORAS.ToString();
tutor = registro.TUTOR;
}
<h1>Mantenimiento cursos</h1>
<form action="~/Cursos.cshtml" method="post">
<label>Código curso:</label>
<input type="text" name="cod_cur" id="cod_cur" value="#codcur" /><br />
<label>Descripción:</label>
<input type="text" name="descripcion" id="descripcion" value="#descripcion" /><br />
<label>Horas:</label>
<input type="text" name="horas" id="horas" value="#horas" /><br />
<label>Tutor:</label>
<input type="text" name="tutor" id="tutor" value="#tutor" /><br />
<br /><br />
<input type="submit" name="first" id="first" value="|<" onclick="#first(pos)"/>
<input type="submit" name="prev" id="prev" value="<<" onclick="#prev(pos)"/>
<input type="submit" name="next" id="next" value=">>" onclick="#next(pos)"/>
<input type="submit" name="last" id="last" value=">|" />
<input type="submit" name="borra" id="borra" value="Borrar" />
<input type="submit" name="Modifica" id="mod" value="Modifica" />
<input type="submit" name="Nuevo" id="new" value="Nuevo" />
</form>
#functions{
int next(int pos) {
if(IsPost) {
pos++;
Session["position"] = pos.ToString();
}
return pos;
}
int prev(int pos) {
if(IsPost) {
pos--;
Session["position"] = pos.ToString();
}
return pos;
}
int first(int pos) {
if(IsPost) {
pos = 0;
Session["position"] = pos.ToString();
}
return pos;
}
}
}
</body>
My problem is when I click on the buttons with the onclick events, all the methods declared in "#functions" are called in reverse order, even when the page is loaded for the first time. I tried to separate them on diferent #functions, use #helper on each method instead of #functions and nothing works.
Anyone have any idea of why is this happening or knows any way to do this propely?
Thanks in advance.
Best regards.
You need to create a postback method with your controller
so lets say this is your controller you would do something like this
public ActionResult YourPageName(string NextAction)
{
if(NextAction == "next")
{
Session["position"] = (int)Session["position"] + 1;
}
elseif(NextAction == "prev")
{
Session["position"] = (int)Session["position"] - 1;
}
else
{
Session["position"] = 0;
}
return View();
}
And change your View to send the right text with your button value when its clicked on

FileUpload: keep filenames when showing validation errors

I have the following file-upload code in C# using Asp.Net MVC. The problem is that when validation errors are shown, all the files chosen by the user are lost (input boxes are cleared). Is it possible to keep the input filenames in their original ordering without using javascript? What's the simplest approach?
Controller code
[HttpPost]
public ActionResult Index(IEnumerable<HttpPostedFileBase> files)
{
//check for errors: if errors, ModelState.AddModelError(...);
if (!ModelState.IsValid) {
return View(files);
}
else {
//..........
}
}
View snippet
#using (Html.BeginForm("Index", "Uploader", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
<input type="file" name="files" id="file1" />
#Html.ValidationMessage("1")
</div>
<div class="form-group">
<input type="file" name="files" id="file2" />
#Html.ValidationMessage("2")
</div>
//and 2 more file input fields
<div>
<input type="submit" value="Upload Files" class="btn btn-success btn-lg" />
</div>
}
In postback you can not retain same local path which will remain in type="file".
to do this think there are two way
1) in your current code if you find any file attached then save on server and maintain some flag in hidden field and hide/show your file control with text box( having only filename)
and send it back to browser. Then on next valid submit take that file name that you have already saved. and do your process.
2) On submit of form, copy(DOM copy) all your html control(file ,textbox, hidenfield ect..) in one iframe and submit that iframe.
In case anybody is still in search of a possibility, here is the work around that worked for me. I'm using MVC5. The idea is to use a session variable. I got the idea from ASP.Net Form.
My Model/ViewModel (only relevant properties):
public partial class emp_leaves
{
public string fileNameOrig { get; set; }
public byte[] fileContent { get; set; }
public HttpPostedFileBase uploadFile { get; set; }
}
In my controller (HttpPost):
//Check
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(emp_leaves emp_leaves)
{
if (emp_leaves.uploadFile != null && emp_leaves.uploadFile.ContentLength>0 && !string.IsNullOrEmpty(emp_leaves.uploadFile.FileName))
{
emp_leaves.fileNameOrig = Path.GetFileName(emp_leaves.uploadFile.FileName);
emp_leaves.fileContent = new byte[emp_leaves.uploadFile.ContentLength];
emp_leaves.uploadFile.InputStream.Read(emp_leaves.fileContent, 0, emp_leaves.uploadFile.ContentLength);
Session["emp_leaves.uploadFile"] = emp_leaves.uploadFile; //saving the file in session variable here
}
else if (Session["emp_leaves.uploadFile"] != null)
{//if re-submitting after a failed validation you will reach here.
emp_leaves.uploadFile = (HttpPostedFileBase)Session["emp_leaves.uploadFile"];
if (emp_leaves.uploadFile != null && emp_leaves.uploadFile.ContentLength>0 && !string.IsNullOrEmpty(emp_leaves.uploadFile.FileName))
{
emp_leaves.fileNameOrig = Path.GetFileName(emp_leaves.uploadFile.FileName);
emp_leaves.uploadFile.InputStream.Position = 0;
emp_leaves.fileContent = new byte[emp_leaves.uploadFile.ContentLength];
emp_leaves.uploadFile.InputStream.Read(emp_leaves.fileContent, 0, emp_leaves.uploadFile.ContentLength);
}
}
//code to save follows here...
}
Finally within my edit view: here, i am conditionally showing the file upload control.
< script type = "text/javascript" >
$("#removefile").on("click", function(e) {
if (!confirm('Delete File?')) {
e.preventDefault();
return false;
}
$('#fileNameOrig').val('');
//toggle visibility for concerned div
$('#downloadlrfdiv').hide();
$('#uploadlrfdiv').show();
return false;
}); <
/script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
#model PPMSWEB.Models.emp_leaves #{ HttpPostedFileBase uploadFileSession = Session["emp_leaves.uploadFile"] == null ? null : (HttpPostedFileBase)Session["emp_leaves.uploadFile"]; } #using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data"
})) { #Html.AntiForgeryToken()
<div class="row">
#*irrelevant content removed*#
<div id="downloadlrfdiv" #((!String.IsNullOrEmpty(Model.fileNameOrig) && (Model.uploadFile==n ull || uploadFileSession !=null)) ? "" : "style=display:none;")>
<label>Attachment</label>
<span>
<strong>
<a id="downloadlrf" href="#(uploadFileSession != null? "" : Url.Action("DownloadLRF", "emp_leaves", new { empLeaveId = Model.ID }))" class="text-primary ui-button-text-icon-primary" title="Download attached file">
#Model.fileNameOrig
</a>
</strong>
#if (isEditable && !Model.readonlyMode)
{
#Html.Raw("&nbsp");
<a id="removefile" class="btn text-danger lead">
<strong title="Delete File" class="glyphicon glyphicon-minus-sign"> </strong>
</a>
}
</span>
</div>
<div id="uploadlrfdiv" #(!(!String.IsNullOrEmpty(Model.fileNameOrig) && Model.uploadFile==n ull) && !Model.readonlyMode ? "" : "style=display:none;")>
<label>Upload File</label> #Html.TextBoxFor(model => model.uploadFile, new { #type = "file", #class = "btn btn-default", #title = "Upload file (max 300 KB)" }) #Html.ValidationMessageFor(x => x.uploadFile)
</div>
</div>
}

Non-Sequential List in Model

I have a list of Guitar objects in my view model.
public List<Guitar> Guitars { get; set; }
The user is able to create these by clicking a button (Thanks to JQuery clone()). I noticed if they remove the 1st list item ([0]) The model returns a null list or if they remove something in the middle of the list like [1], the model only returns item [0] in the list.
I see in the raw request that all of the items exist so I guess I have 2 choices - Maybe someone has a different approach?
1. Operate on the raw Request array in the controller like this:
[HttpPost]
public ActionResult Index(CustomerViewModel customer)
{
var guitars = new List<Guitar>();
var listValues = new List<string>();
var numGuitars = 0;
//Loop through all Request keys in the POST
foreach (string key in Request.Form.AllKeys)
{
//Save any that are part of the Guitar object
if (key.StartsWith("Guitars["))
{
listValues.Add(key);
}
}
//Guitar object has 3 properties so divide by 3 to get total object count
numGuitars = (int)Math.Ceiling(listValues.Count / 3.0);
for (int i = 0; i < numGuitars; i++)
{
var guitarMake = Request["Guitars[" + i + "].Make"];
var guitarModel = Request["Guitars[" + i + "].Model"];
var guitarProductonYear = Request["Guitars[" + i + "].ProductionYear"];
if (!String.IsNullOrEmpty(guitarMake) &&
!String.IsNullOrEmpty(guitarModel) &&
!String.IsNullOrEmpty(guitarProductonYear))
{
var g = new Guitar
{
Make = guitarMake,
Model = guitarModel,
ProductionYear = Int32.Parse(guitarProductonYear)
};
guitars.Add(g);
}
}
2. When a user deletes an item, use JQuery to reassign list indices so we are sequential.
3. Anything else?
Form HTML
<div id="guitars_1" style="display: block;">
<input type="text" value="" name="Guitars[0].Make" id="Guitars_0__Make" placeholder="Make">
<input type="text" value="" name="Guitars[0].Model" id="Guitars_0__Model" placeholder="Model">
</div>
<div id="guitars_2" style="display: block;">
<input type="text" value="" name="Guitars[1].Make" id="Guitars_1__Make" placeholder="Make">
<input type="text" value="" name="Guitars[1].Model" id="Guitars_1__Model" placeholder="Model">
</div>
<div id="guitars_3" style="display: block;">
<input type="text" value="" name="Guitars[2].Make" id="Guitars_2__Make" placeholder="Make">
<input type="text" value="" name="Guitars[2].Model" id="Guitars_2__Model" placeholder="Model">
</div>
<!-- Start Add Guitar Row Template -->
<div style="display:none">
<div id="guitarsTemplate">
<div class="formColumn1"><label>Guitar</label></div>
<div class="formColumn2">#Html.TextBoxFor(model => model.Guitars[0].Make, new { Placeholder = "Make" })
<div class="messageBottom">
#Html.ValidationMessageFor(model => model.Guitars[0].Make)
</div>
</div>
<div class="formColumn3">#Html.TextBoxFor(model => model.Guitars[0].Model, new { Placeholder = "Model" })
<div class="messageBottom">
#Html.ValidationMessageFor(model => model.Guitars[0].Model)
</div>
</div>
<div class="formColumn4">#Html.TextBoxFor(model => model.Guitars[0].ProductionYear, new { Placeholder = "Production Year" })
<div class="messageBottom">
#Html.ValidationMessageFor(model => model.Guitars[0].ProductionYear)
</div><a class="icon delete">Delete</a>
</div>
</div>
</div>
<!-- End Add Guitar Row Template -->
JS that clones and deletes items
$(document).ready(function() {
var uniqueId = 1;
var ctr = 0;
$(function() {
$('.js-add-guitar-hyperlink').click(function() {
var copy = $("#guitarssTemplate").clone(true).appendTo("#addGuitarSection").hide().fadeIn('slow');
var guitarDivId = 'guitars_' + uniqueId;
var copyText = copy.html();
copyText = copyText.replace(/Guitars\[0\]/g, 'Guitars[' + ctr + ']');
copyText = copyText.replace('Guitars_0', 'Guitars_' + ctr);
copy.html(copyText);
$('#guitarsTemplate').attr('id', guitarDivId);
var deleteLink = copy.find("a.icon.delete");
deleteLink.on('click', function() {
copy.fadeOut(300, function() { $(this).remove(); }); //fade out the removal
});
$('#' + cosponsorDivId).find('input').each(function() {
//$(this).attr('id', $(this).attr('id') + '_' + uniqueId);
// $(this).attr('name', $(this).attr('name') + '_' + uniqueId);
});
uniqueId++;
ctr++;
});
});
});
For this kind of dynamic list management in MVC, you could do worse than take a look at the BeginCollectionItem HtmlHelper:
https://www.nuget.org/packages/BeginCollectionItem/
https://github.com/danludwig/BeginCollectionItem
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

Categories

Resources