How to handle multiple dynamic form with c# - c#

I want to have a page on my website where I can upload files. For each file I want to have a name and a category.
[Required(ErrorMessage = "Please choose a file")]
[Display(Name = "File")]
public HttpPostedFileBase file { get; set; }
[Required(ErrorMessage = "A name is required")]
[Display(Name = "Name")]
public string name { get; set; }
[Display(Name ="Category")]
public string cat { get; set; }
This is my model. I want to have some dynamic form, what I mean is a form with a button that allows the user to add another form on the page to upload multiple files with a name and a category for each file. I've done this with Symfony2, but I have no idea how to do it with ASP.NET. Can someone help me please ?

At first create another model like following:
public class fileListModel{
IList<yourModel> fileList {get;set;}
}
Then in the razor view create form like this way:
#model fileListModel
<form>
//dynamic html(you can also use partial for this). When button will be clicked append following html using jquery $(form).append()
#{var key = [use some random id or guid]}
<input type="hidden" name="fileList.Index" value="#key" />
<input type="text" name="fileList[#key].name" value="Name" />
<input type="text" name="fileList[#key].cate" value="Category" />
<input type="file" name="fileList[#key].file" value="Upload"/>
// end dynamic html
#{ key = [use some random id or guid]}
<input type="hidden" name="fileList.Index" value="#key" />
<input type="text" name="fileList[#key].name" value="Name" />
<input type="text" name="fileList[#key].cate" value="Category" />
<input type="file" name="fileList[#key].file" value="Upload"/>
// end dynamic html
</form>
Now create a controller action method to accept the fileList:
public ActionResult upload(fileListModel fileList){
//save them to db
}

The following is a bare minimum example based on this blogpost. For demo purposes, I've named my model Foo. So whenever you read this, this should be your model with file, name and cat properties.
First, add https://www.nuget.org/packages/BeginCollectionItem/ to your project.
Then, add a partial view to your Views folder. I've named mine "_AddFile.cshtml":
#model WebApplication2.Models.Foo
#using (Html.BeginCollectionItem("files"))
{
<div class="form-horizontal">
<fieldset>
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
</div>
#Html.LabelFor(model => model.Cat, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Cat, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
</fieldset>
</div>
}
Note, the Html.BeginCollectionItem("files"), this is creating a collection, that is later grouped together and bound to your model named "files".
Our controller looks like this:
public ActionResult Index()
{
//Initialize the view with an empty default entry
var vm = new List<Foo> {
new Models.Foo {
Cat ="foo",
Name =" bar"
}
};
return View(vm);
}
//this calls your partial view and initializes an empty model
public PartialViewResult AddFile()
{
return PartialView("_AddFile", new Foo());
}
//note "files" name? The same as our collection name specified earlier
[HttpPost]
public ActionResult PostFiles(IEnumerable<Foo> files)
{
//do whatever you want with your posted model here
return View();
}
In your view, use this form:
#model IEnumerable<WebApplication2.Models.Foo>
#using (Html.BeginForm("PostFiles","Home", FormMethod.Post))
{
<div id="FileEditor">
#foreach (var item in Model)
{
Html.RenderPartial("_AddFile", item);
}
</div>
<div>
#Html.ActionLink("Add File", "AddFile", null, new { id = "addFile" }) <input type="submit" value="Finished" />
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script>
$(function () {
$("#addFile").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) { $("#FileEditor").append(html); }
});
return false;
});
})
</script>
}
The foreach loop renders a partial View for each model entry, in our case just one with a default entry.
The javascript loop then calls our PartialView and renders an empty template below the existing ones.
A call to submit, then lets you deal with your files in any way you want:

Related

Passing selected string from dropdown to controller

I am displaying a dropdown list from my controller as follows:
public ActionResult Index()
{
var title = new List<String>()
{
"English", "French", "Spanish"
};
List<SelectListItem> languageList = new List<SelectListItem>();
string defaultTitle = "Language";
foreach (var item in title)
{
languageList.Add(new SelectListItem()
{
Text = item,
Value = item,
Selected = (item == defaultTitle ? true : false)
});
}
ViewBag.LanguageList = languageList;
return View();
}
My View is as follows:
#using (Html.BeginForm("GetStatusTrad", "StatusTradController", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Translation</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.Label("Language")
#Html.DropDownList("lang", new SelectList(ViewBag.LanguageList, "Text", "Value"), "Language")
</div>
</div>
<div></div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
It displays the dropdown correctly, but when I want to pass the selected value to an action of the controller, I get a 404 error.
My action is :
public ActionResult GetStatusTrad(string language)
{
*some code*
}
Any idea why I can't pass the selected value of the dropdown to the controller?
Your helper should be:
#Html.DropDownList("language", <-- note this
new SelectList(ViewBag.LanguageList, "Text", "Value"), "Language")
It happend becose basically your helper will be rendered to input like this:
<select name="language">
...
</select>
And on form POST your controller will be able to bind your values based on name property of input.
#Html.DropDownList("lang", new SelectList(... generates a <select> with name="lang. You need to change the parameter in the POST method to match
public ActionResult GetStatusTrad(string lang)
As a side note, LanguageList is already IEnumerable<SelectListItem> so using new SelectList() to create another identical one is pointless. I can be just
#Html.DropDownList("lang", (IEnumerable<SelectListItem>)ViewBag.LanguageList, "Language")
You also have a typo in the BeginForm() method. It needs to be
#using (Html.BeginForm("GetStatusTrad", "StatusTrad", FormMethod.Post))
i.e. "StatusTrad", not "StatusTradController" (unless you have really named your controller StatusTradControllerController)
you can use strongly type view to return selected dropdown value.
create simple class like below
public class myModel
{
public string language { get; set; }
....
....
}
Then use this class/model in View
#model yourProject.Models.myModel
<div class="form-group">
<label class="col-lg-2 control-label">Language</label>
<div class="col-lg-5">
#Html.ValidationMessageFor(m => m.language)
#Html.DropDownListFor(m => m.language, new SelectList(ViewBag.LanguageList, "Text", "Value"), "-- Select --", new { #class = "form-control" })
</div>
</div>
Controller method look like below
[HttpPost]
public ActionResult GetStatusTrad(myModel model)
{
*some code*
}

How can I pass string value for "asp-for" in asp net 5

I want to write a Edit.cshtml file for an entity with many properties to edit, so I have to write the following codes many times:
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
Actually, there are many entities so that I have to write many Edit.cshtml files. I want to make some simplifications
I want to select some properties of the entity in the controller and use loop to show the properties in the view. For example:
In the controller file:
public IActionResult Edit(string id)
{
var model = GetModel(id);
var propertyNames= new List<string>()
{
"Name",
"Email"
// add some other property names of the entity
};
ViewData["PropertyList"] = propertyNames;
return View(model);
}
In the view file:
#{
var propertyNames = (List<string>)ViewData["PropertyList"];
foreach (string item in propertyNames)
{
<div class="form-group">
<label asp-for="#(item)" class="col-md-2 control-label"></label>
<div class="col-md-3">
<input asp-for="#(item)" class="form-control" />
<span asp-validation-for="#(item)" class="text-danger"></span>
</div>
</div>
}
}
but it cannot work, since it generates wrong codes. It seems that I cannot pass a string value for "asp-for" tag helper.
For example, if I change the code of top to this:
#{
string e = "Email";
<div class="form-group">
<label asp-for="#e" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="#e" class="form-control" />
<span asp-validation-for="#e" class="text-danger"></span>
</div>
</div>
}
The code above will generate this:
<div class="form-group">
<label class="col-md-2 control-label" for="e">e</label>
<div class="col-md-10">
<input class="form-control" type="text" id="e" name="e" value="Email" />
<span class="text-danger field-validation-valid" data-valmsg-for="e" data-valmsg-replace="true"></span>
</div>
</div>
The expected code is:
<div class="form-group">
<label class="col-md-2 control-label" for="Email">Email</label>
<div class="col-md-10">
<input class="form-control" type="email" data-val="true" data-val-email="Email 字段不是有效的电子邮件地址。" data-val-required="Email 字段是必需的。" id="Email" name="Email" value="" />
<span class="text-danger field-validation-valid" data-valmsg-for="Email" data-valmsg-replace="true"></span>
</div>
</div>
How should I do?
Is it possible in razor?
Ok, I managed to get this working. DISCLAIMER: It is super hacky and I have no idea if I've done it in the best way possible. All I know is that it does what you want and it might point you in the right direction.
Firstly, I created a model:
using System.ComponentModel.DataAnnotations;
namespace WebApplication1.Models
{
public class TestModel
{
[Required]
public string Name { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}
Then, I made a custom tag helper. This is the horrible bit where the "magic" happens. Specifically the first section of the Process method...
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Razor.TagHelpers;
using System.Linq;
namespace WebApplication1.TagHelpers
{
[HtmlTargetElement("edit")]
public class EditTagHelper : TagHelper
{
[HtmlAttributeName("asp-for")]
public ModelExpression aspFor { get; set; }
[ViewContext]
[HtmlAttributeNotBound]
public ViewContext ViewContext { get; set; }
protected IHtmlGenerator _generator { get; set; }
public EditTagHelper(IHtmlGenerator generator)
{
_generator = generator;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var propName = aspFor.ModelExplorer.Model.ToString();
var modelExProp = aspFor.ModelExplorer.Container.Properties.Single(x => x.Metadata.PropertyName.Equals(propName));
var propValue = modelExProp.Model;
var propEditFormatString = modelExProp.Metadata.EditFormatString;
var label = _generator.GenerateLabel(ViewContext, aspFor.ModelExplorer,
propName, propName, new { #class = "col-md-2 control-label", #type = "email" });
var input = _generator.GenerateTextBox(ViewContext, aspFor.ModelExplorer,
propName, propValue, propEditFormatString, new { #class = "form-control" });
var validation = _generator.GenerateValidationMessage(ViewContext, aspFor.ModelExplorer,
propName, string.Empty, string.Empty, new { #class = "text-danger" });
var inputParent = new TagBuilder("div");
inputParent.AddCssClass("col-md-10");
inputParent.InnerHtml.Append(input);
inputParent.InnerHtml.Append(validation);
var parent = new TagBuilder("div");
parent.AddCssClass("form-group");
parent.InnerHtml.Append(label);
parent.InnerHtml.Append(inputParent);
output.Content.SetContent(parent);
base.Process(context, output);
}
}
}
NB: To make the custom TagHelper work, you need to add a line into the _ViewImports.cshtml file, like this (replace WebApplication1 with your namespace):
#addTagHelper "*, WebApplication1"
I changed my action to this, to sort of match yours (maybe you can use reflection to get your model property names here?):
public IActionResult Index()
{
var propertyNames = new List<string>()
{
"Name",
"Email"
};
ViewData["PropertyList"] = propertyNames;
var m = new TestModel()
{
Name = "huoshan12345",
Email = "test#test.net"
};
return View(m);
}
Then finally, in the view, you can do something like this:
<div class="row">
#using (Html.BeginForm())
{
var propertyNames = (List<string>)ViewData["PropertyList"];
foreach (string item in propertyNames)
{
<edit asp-for="#item"></edit>
}
<input type="submit" value="Submit" />
}
</div>
You can also try this:
foreach (string item in propertyNames){
#Html.TextBox(item, value: null, htmlAttributes: new { #class = "form-control" })
}
Yes, it is possible to write it with razor.
think this will help you. If you don't put the "type" attribute it will generate it with type='text'. You can also hard code your data-val attributes, but it is not recommended just replace the '-' with '_' Ex: #data_val_email
<div class="form-group">
#Html.LabelFor(x=>x.Email)
<div class="col-md-10">
#Html.TextBoxFor(x => x.Email, new { #class = "form-control", #type = "email" })
#Html.ValidateFor(x=>x.Email)
</div>
</div>
Here's a related technique. I extended the tag helper to inject the required HTML into the page. This works a bit like the ASP.NET MVC EditorTemplate.
Here's the custom tag helper that injects a special partial view
public class MyFormGroupTagHelper : PartialTagHelper
{
public MyFormGroupTagHelper(ICompositeViewEngine viewEngine, IViewBufferScope viewBufferScope) : base(viewEngine, viewBufferScope)
{ }
public ModelExpression Property { get; set; }
public string LabelText { get; set; } = null;
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
this.For = Property;
this.Name = "_FormGroup";
// Have to use Viewdata to pass information to the partial view, because the model is the single property of the entity that will be posted back to the controller
this.ViewData["TH_LabelText"] = LabelText;
this.ViewData["TH_DataTypeName"] = Property.Metadata.DataTypeName;
await base.ProcessAsync(context, output);
}
}
Here's the partial view _FormGroup.cshtml. This generates the markup for a single field on a form with the Bootstrap styles. It also attaches the "datepicker" class to the input tag if the field is a date. Notice how asp-for is populated with #Model so this same view can be bound to any property on any entity model
#model object
#{
string LabelText = (string)#ViewData["TH_LabelText"];
string DataTypeName = (string) ViewData["TH_DataTypeName"];
bool IsDate = (DataTypeName == "Date");
}
<div class="form-group row">
#if (LabelText != null)
{
<label asp-for="#Model" class="control-label col-md-4">#LabelText</label>
}
<div class="col-md-8">
<input asp-for="#Model" class="form-control #( IsDate ? "datepicker" : "")" />
<span asp-validation-for="#Model" class="text-danger"></span>
</div>
</div>
Now in the Create or Edit view, where the model is the business entity, you can create the code to edit a single property on the entity like this.
<my-form-group label-text="Result date" property="ResultDate" view-data="ViewData" ></my-form-group>
In this case the model is a class with a field called ResultDate defined like this:
[DataType(DataType.Date)]
public DateTime? ResultDate { get; set; }
Note that you must set the view-data attribute to pass the ViewData object into tag helper. It uses ViewData to pass information on to the partial view. This isn't ideal but I couldn't think of any other way to pass information through to the partial view. I prefixed the keys with "TH_" to prevent the tag helper overwriting any other values in Viewdata.

Double URL parameters on second controller method call

I have a controller which is called on submit of a search field in my index view.
[System.Web.Mvc.HttpGet]
public ActionResult ShowResultsWithFilter(SearchModelWithFilters searchModel)
{
//Logic and setting viewbag values
return View(searchModel);
}
The url returns with my results.
as
"url/Result/ShowResultsWithFilter?searchString=ASTRINGTOSEARCH"
My submit model
public class SearchModelWithFilters
public string searchString { get; set; }
public bool filterA { get; set; }
public bool filterB { get; set; }
public bool filterC {get; set;}
public SearchModelWithFilters()
{
//My initial values which should be defaut when first loading the results page
filterA = true;
filterB = true;
filterC = true;
}
}
The search and filter is also on the results page, so i then resubmit with new or the same details whilst using the same controller and the url returns as
"url/Result/ShowResultsWithFilter?searchString=ASTRINGTOSEARCH&filterA=false&filterA=true&filterB=false&filterB=true&filterC=false&filterC=true"
The first instance of each filter is always false and the second updates based on the values which are submitted.
I would like it to only show the correctly updated parameters(my filters), rather then double.
My search field view (SearchField)
#model CodeCompareUk.Models.SearchModelWithFilters
<div class="row">
<div class="col-md-12">
<div class="input-group input-group-lg">
#Html.TextBoxFor(m => m.searchString, new { #class = "form-control", #placeholder = "Enter Search..." })
<a href="javascript:$('form').submit();" class="input-group-addon">
<i class="fa fa-search"></i>
</a>
</div>
</div>
</div>
I place it inside this form helper and in the results page also include the partial "refine search"
#using (Html.BeginForm("ShowResultsWithFilter", "Result", method: FormMethod.Get))
{
#Html.Partial("SearchField")
//also this is only added in results page
#Html.Partial("RefineSearch")
}
My refine search (RefineSearch)
#model CodeCompareUk.Models.SearchSubmitModelWithFilters
#Html.CheckBoxFor(me => me.FilterA, new { #class = "custom-checkbox" })
#Html.CheckBoxFor(me => me.FilterB, new { #class = "custom-checkbox" })
#Html.CheckBoxFor(me => me.FilterC, new { #class = "custom-checkbox" })
Thanks
This is how CheckBoxFor works. It creates 2 <inputs> - one "hidden" and second "checkbox". Note that normally forms are submited with "POST" not "GET". Anyways, see more info about CheckBoxFor here:
Why does the CheckBoxFor render an additional input tag, and how can I get the value using the FormCollection?

Partial views and html forms Asp Mvc

I am attempting to render a partial view that contains a simple html form. I want to render the form from a controller as I handle the postback from an overloaded controller method. I have tried #Html.Action("ContactForm")but I get an Exception because child actions cannot redirect.
My Controller:
public ActionResult Index()
{
return View();
}
[HttpGet]
public ActionResult ContactForm()
{
return PartialView(new ContactForm());
}
[HttpPost]
public ActionResult ContactForm(ContactForm Contact)
{
return RedirectToAction("FormResults", new { ContactForm = Contact });
}
public ActionResult FormResults(ContactForm Contact)
{
return PartialView(Contact);
}
My Form:
#using(Html.BeginForm())
{
<h2>Contact Form</h2>
<div class="input-group">
<h4>#Html.LabelFor(m => m.FirstName, "First Name")</h4>
#Html.TextBoxFor(m => m.FirstName, new { #class = "form-control", #placeholder = "First Name" })
</div>
<div class="input-group">
<h4>#Html.LabelFor(m => m.LastName, "Last Name")</h4>
#Html.TextBoxFor(m => m.LastName, new { #class = "form-control", #placeholder = "Last Name" })
</div>
<div class="input-group">
<h4>#Html.LabelFor(m => m.Email, "Email")</h4>
#Html.TextBoxFor(m => m.Email, new { #class = "form-control", #placeholder = "Email", #type = "text" })
</div>
<input type="submit" class="btn btn-info" value="Submit" />
}
Any Help on how I would accomplish this would be appreciated.
try surrounding the form with a div and a certain id and use:
#using(Ajax.BeginForm("ContactForm","YourController",new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "yourCertainId",
HTTPMethod = "POST"
})
and your ActionMethod:
[HttpPost]
public ActionResult ContactForm(ContactForm Contact)
{
return Partial("YourPartialName", Contact });
}
make sure that you include the bundle jqueryval on the bottom of your view.
you wont need the second controller method "FormResults"
Does something like this not work for you?
I don't think you need a redirect.
[HttpPost]
public ActionResult ContactForm(ContactForm Contact)
{
return PartialView("FormResults", Contact);
}
This uses the
PartialView(string viewName, object model)
overload of the PartialView method in the Controller class.
This allows you to use a View that doesn't match the ActionResult's method name.
The same thing works for the plain "View" method as well.

asp.net view syntax and html helper

This is actually two questions in one.
First question is about the following:
<div class="form-group">
#Html.LabelFor(model => model.xxx, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.xxx , new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.xxx, "", new { #class = "text-danger" })
</div>
</div>
I don't understand what model => model.xxx means, I know what it's doing but i don't know how to interpret the syntax.
the second question is, if I have - for example -
foreach (var item in Model)
how can I replace
#Html.EditorFor(model => model.xxx , new { htmlAttributes = new { #class = "form-control" } })
with
#Html.EditorFor(item.stringProperty , new { htmlAttributes = new { #class = "form-control" } })
when I try this, it gives me errors, is there an overloaded EditorFor helper that accepts this?
thank you!
One view can has 0 or 1 Model, which's sending from controller.
public class Person
{
public string Name {get;set;}
public int Age {get;set;}
}
public ViewResult Index()
{
Person p = new Person() { Name = "P1", Age = 100};
return View(p);//
}
if your View's name "Index" then you can use second way for View, which contain 2 parameters:
ViewName and model
return View("Index", model: p);
then in your View you can use the model, if it has been implemented that:
#model Person//and remember about namespace
#
{
...
}
#using(Html.BeginForm("ActionName", "controllerName", FormMethod.Post))
{
#Html.EditorFor(model => model.Name); // it create input, check in F12 in your browse - then you can exactly understand.
}
if you want create Editor for item you must use:
#Html.TextBox("YourName")
for example:
#using(Html.BeginForm("Action", "controller")
{
#Html.TextBox("YourName")
<input type="submit" value="ok"/>
}
and in your controllerController:
public ViewResult Action(string YourName)
{
//here you got value for string YourName
return View();
}
and helpfully answer's here:
ASP.NET MVC get textbox input value
Edit, answer about exactly problem (which containt in comment below question):
I have a list, and I want to display an input text box for each item in the list, but I want each text box to have text inside when its created, text from each item in the list (coming from the item's property)
#foreach(var item in Model)
#using(Html.BeginForm("MyMethod", "Controller"))
{
#Html.TextBox("item", item)
<input type="submit" value="ok" />
}
and in your controller add MyMethod:
[HttpPost]
public ViewResult MyMethod(string item)
{
...
}
or
[HttpPost]
public ViewResult MyMethod(int item) //if it's an int
{
...
}
and if you want to have a better security page please read about Html.AntiForgeryToken:
http://msdn.microsoft.com/en-us/library/dd470175(v=vs.118).aspx
#using(Html...())
{
#Html.AntiForgeryToken()
(...)
}
I see you already got the answer of your first question.
For the second one, i think
#Html.EditorFor(item => item.stringProperty , new { htmlAttributes = new { #class = "form-control" } })
will work fine

Categories