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?
Related
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*
}
I have an MVC view that contains various elements including a checkbox and a text area.
When POST'ing, if the checkbox is selected, the Comment textarea is compulsory.
I created a custom validator that performs this function server-side.
The validator works, and the view returns the validation error correctly when the checkbox is selected and the text area is empty, however when it does return the validation error message, the textarea now has a single comma in it.
I have checked and cannot for the life of me figure out why it is doing it.
As a test I changed the text to a textbox and the problem does not occur.
Is this a bug in the text area input?
For brevity I will endevour to only post the relvant code:
Edit.cshtml
<div class="form-group">
<div class="col-lg-1 col-sm-2 col-md-2">
#Html.CheckBoxFor(x => x.ConditionalPass)
<label class="control-label">Conditional Pass?</label>
</div>
<div class="col-lg-1 col-sm-2 col-md-2">
<label class="control-label">Conditional Pass Comment</label>
</div>
<div class="col-lg-6">
#Html.TextAreaFor(x => x.ConditionalPassComment, new { #rows = 4, #cols = 6, #class = "form-control" })
#Html.HiddenFor(x => x.ConditionalPassComment)
#Html.ValidationMessageFor(m => m.ConditionalPassComment, "A comment is required when conditionally passed", new { #class = "text-danger", #style = "font-size:20px;" })
</div>
</div>
<!-- ./form-group-->
ViewModel
public class BoardFatViewModel
{
public int Id { get; set; }
[Display(Name = "Conditional Pass")]
public bool ConditionalPass { get; set; }
[CommentRequiredIfConditonallyPassed("ConditionalPass", "ConditionalPassComment", ErrorMessage = "An approval comment is required on a conditionally passed FAT.")]
public string ConditionalPassComment { get; set; }
[StringLength(1)]
public string Status { get; set; }
}
Custom Validator
public class CommentRequiredIfConditonallyPassed : ValidationAttribute, IClientValidatable
{
private readonly string[] _properties;
public CommentRequiredIfConditonallyPassed(params string[] properties)
{
_properties = properties;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (_properties == null || _properties.Length < 1)
{
return null;
}
var passStatusPropertyInfo = validationContext.ObjectType.GetProperty(_properties[0]);
var passStatusValue = passStatusPropertyInfo.GetValue(validationContext.ObjectInstance, null).ToString();
if (passStatusValue.ToLower() == "false")
return null;
var approvalCommentPropertyInfo = validationContext.ObjectType.GetProperty(_properties[1]);
var approvalCommentValue = approvalCommentPropertyInfo.GetValue(validationContext.ObjectInstance, null) == null ? "" : approvalCommentPropertyInfo.GetValue(validationContext.ObjectInstance, null).ToString();
if (String.IsNullOrEmpty(approvalCommentValue))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessage,
ValidationType = "atleastonerequired"
};
rule.ValidationParameters["properties"] = string.Join(",", _properties);
yield return rule;
}
}
Attached is a screen shot of the result.
The page source for the text area where the , can be seen
<div class="col-lg-6">
<textarea class="input-validation-error form-control" cols="6" data-val="true" data-val-atleastonerequired="An approval comment is required on a conditionally passed FAT." data-val-atleastonerequired-properties="ConditionalPass,ConditionalPassComment" id="ConditionalPassComment" name="ConditionalPassComment" rows="4">,</textarea>
<input class="input-validation-error" id="ConditionalPassComment" name="ConditionalPassComment" type="hidden" value="" />
<span class="field-validation-error text-danger" data-valmsg-for="ConditionalPassComment" data-valmsg-replace="false" style="font-size:20px;">A comment is required when conditionally passed</span>
</div>
Please let me know if you require any of my other code, but like I said, it I have tested on text area and textbox and it only occurs on the textarea.
Edit 1
As a test, I removed the custom validation code and added a plain old [Required] data annotation and the problem persists, so the problem does not lie with the custom validation.
[Required]
public string ConditionalPassComment { get; set; }
Edit 2.
So it seems to be a problem in the #Html.TextArea and #Html.TextAreaFor helper methods.
If I use the html itself, the problem goes away.
<textarea class="form-control" cols="6" id="ConditionalPassComment" name="ConditionalPassComment" rows="4"></textarea>
<input id="ConditionalPassComment" name="ConditionalPassComment" type="hidden" value="" />
For those who want to perhaps have a look at the source code for the helpers, I am using System.Web.Mvc 5.2.3.0.
I am a beginner in ASP.Net MVC Web Development. And I am stuck at one place where I need to have POST request For EDIT and Delete Requests in the same page.
Behavior:
I have a page with many rows shown in tabular format, Now for each row I wanted to have "Delete", "Edit", and "Details" Button. I have done that successfully. But I read that we should never have a "GET" Request for Deleting and Editing a resource.
Issue:
I know how to make a POST Form using #Html.BeginForm(....FormMethod.POST,..), But I am confused in my case because I have many rows with each row having "Edit" and "Delete".
Below is my Attempt:
Parent View:
#model IEnumerable<Bridge.Models.Resume>
#using Bridge.ViewModels
<table class="table">
foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.datetime)
</td>
#Html.Partial("_TableButtonsPartial", new SmallButtonViewModel
{
ResumeId = item.ResumeId
})
</tr>
}
</table>
TableButtonPartial View:
#model Bridge.ViewModels.SmallButtonViewModel
#using Bridge.ViewModels
<td style="width: 120px;">
<div class="btn-group" role="group">
#Html.Partial("_SmallButtonPartial",
new SmallButtonViewModel
{
Action = "Edit",
ButtonType = "btn-primary",
Glyph = "pencil",
Text = "Edit button",
ReferralId = Model.ReferralId,
})
#Html.Partial("_SmallButtonPartial",
new SmallButtonViewModel
{
Action = "Details",
ButtonType = "btn-success",
Glyph = "list",
Text = "Detail button",
ResumeId = Model.ResumeId,
})
#Html.Partial("_SmallButtonPartial",
new SmallButtonViewModel
{
Action = "Delete",
ButtonType = "btn-danger",
Glyph = "trash",
Text = "Delete button",
ResumeId = Model.ResumeId,
})
</div>
</td>
SmallButtonPartial View
#model Bridge.ViewModels.SmallButtonViewModel
<a type="button" class="btn #Model.ButtonType btn-sm"
href="#Url.Action(Model.Action)#Model.ActionParameters">
<span class="glyphicon glyphicon-#Model.Glyph">
</span><span class="sr-only">#Model.Text</span>
</a>
SmallButton ViewModel
public class SmallButtonViewModel
{
public string Action { get; set; }
public string Text { get; set; }
public string Glyph { get; set; }
public string ButtonType { get; set; }
public int? ResumeId { get; set; }
public string ActionParameters
{
get
{
var param = new StringBuilder("?");
if (ResumeId != null & ResumeId > 0)
param.Append(string.Format("{0}={1}&", "resumeId", ResumeId));
return param.ToString().Substring(0, param.Length - 1);
}
}
}
Controller
public FileContentResult Details(int? resumeId)
{
var temp = _context.Resumes.Where(f => f.ResumeId == resumeId).SingleOrDefault();
var fileRes = new FileContentResult(temp.Content.ToArray(), temp.ContentType);
fileRes.FileDownloadName = temp.FileName;
return fileRes;
}
// Todo: Need to make it a POST Request
public ActionResult Delete(int? resumeId)
{
var r = _context.Resumes.Where(c => c.ResumeId == resumeId);
_context.Resumes.RemoveRange(r);
_context.SaveChanges();
return RedirectToAction("ResumeCenter");
}
My Thinking
I want to be able to do something like below in SmallButtonView :
#model Bridge.ViewModels.SmallButtonViewModel
#Html.BeginForm(..,FormMethod.POST,..) // but I am not sure how to achieve it?
You would start by making sure the Delete action could only be reached by a POST request, using the HttpPostAttribute:
[HttpPost]
public ActionResult Delete(int? resumeId)
{
var r = _context.Resumes.Where(c => c.ResumeId == resumeId);
_context.Resumes.RemoveRange(r);
_context.SaveChanges();
return RedirectToAction("ResumeCenter");
}
Once you add that attribute, any attempts to issue a GET request to that action will result in a 404 (Not Found) response.
Then, replace this:
#Html.Partial("_SmallButtonPartial",
new SmallButtonViewModel
{
Action = "Delete",
ButtonType = "btn-danger",
Glyph = "trash",
Text = "Delete button",
ResumeId = Model.ResumeId,
})
with something like:
#using(Html.BeginForm("delete", "your controller name", FormMethod.Post, new { #class="delete-form" }))
{
<input type="hidden" name="ResumeId" value="#Model.ResumeId" />
<button type="submit" class="btn btn-danger">
<span class="glyphicon glyphicon-trash"></span>
Delete
</button>
}
You can have as many forms on a page as you like, as long as they are not nested - this generates a form on each row. I added a class here (delete-form) that you could use to hook into a jQuery event, if you wanted to handle this via AJAX instead of allowing the form to submit.
Any other actions, like "view" or "edit", would still just be GET requests, which you can leave as anchors, or use forms with the method set to GET (the end result is the same).
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:
TL;DR: How do I handle form data that is being submitted with nonstandard names for the data?
The stats:
MVC 5
ASP.NET 4.5.2
I am bringing in two different models:
public async Task<ActionResult> Index() {
var prospectingId = new Guid(User.GetClaimValue("CWD-Prospect"));
var cycleId = new Guid(User.GetClaimValue("CWD-Cycle"));
var viewModel = new OnboardingViewModel();
viewModel.Prospecting = await db.Prospecting.FindAsync(prospectingId);
viewModel.Cycle = await db.Cycle.FindAsync(cycleId);
return View(viewModel);
}
One called Prospecting, the other called Cycle. The Prospecting one is working just fine, as nothing else on the page needs it except one small item.
The Cycle has a mess of separate forms on the page, each needing to be separately submittable, and editing just one small part of the Cycle table. My problem is, I don't know how to submit the correct data to the backend. I am also not entirely sure how to "catch" that data.
The bright spot is that apparently the front end is properly reflective of what is in the db. As in, if I manually change the db field to a true value, the checkbox ends up being selected on refresh.
My current form is such:
#using(Html.BeginForm("UpdatePDFResourceRequest", "Onboarding", FormMethod.Post, new { enctype = "multipart/form-data" })) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<fieldset>
#Html.LabelFor(Model => Model.Cycle.PDFResourceLibrary, htmlAttributes: new { #class = "control-label" })
#Html.CheckBoxFor(Model => Model.Cycle.PDFResourceLibrary, new { #class = "form-control" })
#Html.ValidationMessageFor(Model => Model.Cycle.PdfResourceLibrary, "", new { #class = "text-danger" })
<label class="control-label"> </label><button type="submit" value="Save" title="Save" class="btn btn-primary glyphicon glyphicon-floppy-disk"></button>
</fieldset>
}
But the resulting HTML is such:
<input id="Cycle_PDFResourceLibrary" class="form-control" type="checkbox" value="true" name="Cycle.PDFResourceLibrary" data-val-required="'P D F Resource Library' must not be empty." data-val="true">
As you can see, the name= is Cycle.PDFResourceLibrary and I don't know how to catch this on the backend.
My model for that specific form is:
public class PDFResourceRequestViewModel {
[DisplayName("PDF Resource Library Request")]
public bool PDFResourceLibrary { get; set; }
[DisplayName("Date Requested")]
[DataType(DataType.Date)]
public DateTime PDFResourceLibraryDate { get; set; }
[DisplayName("Notes")]
public string PDFResourceLibraryNotes { get; set; }
}
(not the overall model for that table, though)
And the method used to handle the form submission is:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> UpdatePDFResourceRequest(PDFResourceRequestViewModel model) {
var id = new Guid(User.GetClaimValue("CWD-Cycle"));
Cycle cycle = await db.Cycle.FindAsync(id);
if(cycle == null) {
return HttpNotFound();
}
try {
cycle.CycleId = id;
cycle.PDFResourceLibrary = model.PDFResourceLibrary;
cycle.PDFResourceLibraryDate = DateTime.Now;
cycle.PDFResourceLibraryNotes = model.PDFResourceLibraryNotes;
db.Cycle.Add(cycle);
await db.SaveChangesAsync();
return RedirectToAction("Index");
} catch { }
return View(model);
}
Now, I know that the method is wrong, for one I am editing just three values out of dozens in that table, so I need to be using something like this method. Problem is, the form is getting submitted with the name= of Cycle.PDFResourceLibrary and it is not being matched up on the back end.
Help?
You can use the [Bind(Prefix="Cycle")] attribute to 'strip' the prefix so that name="Cycle.PDFResourceLibrary" effectively becomes name="PDFResourceLibrary" and will bind to your PDFResourceRequestViewModel
public async Task<ActionResult> UpdatePDFResourceRequest([Bind(Prefix="Cycle")]PDFResourceRequestViewModel model)