I'm using Fluent Validation in my ASP.NET MVC application. I need to change the CSS class of the control when there's an error. Please see my code below.
using FluentValidation;
using Hrms.Framework.Common;
using Hrms.Web.Models;
namespace Hrms.Web.InputValidators
{
public class AnnouncementValidator : AbstractValidator<AnnouncementModel>
{
public AnnouncementValidator()
{
RuleFor(a => a.Title).NotEmpty().WithMessage("Announcement Title is required");
RuleFor(a => a.Title).MaximumLength(50).WithMessage(string.Format("Announcement Title should be 50 characters or less", 50));
}
}
}
Here is the code I have in the view (HTML)
#model Hrms.Web.Models.AnnouncementModel
#{
Layout = "~/Views/Shared/_Application.cshtml";
}
#using (Ajax.BeginForm("Save", "Announcement", new AjaxOptions
{
HttpMethod = "POST",
OnFailure = "saveFailed",
OnSuccess = "saveSucc",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "result"
}))
{
#Html.AntiForgeryToken()
<div id="result"></div>
<label>#Resources.Organization.Announcement.Title</label>
#Html.TextBoxFor(m => m.Title, new { maxlength = #GlobalConstants.AnnouncementTitleMaxLength })
#Html.ValidationMessageFor(model => model.Title, null, new { #class = "errVal" })
<input type="submit" value="Save">
}
#section scripts {
<script type="text/javascript">
$(document).ready(function () {
$("#SendingDate").datepicker();
});
function saveSucc(m) {
$("#Title").val("");
$("#Body").val("");
$("#SendingDate").val("");
}
function saveFailed() {
alert("saveFailed");
}
</script>
}
Any help is highly appreciated
When I tried to edit this post, it showed an error saying that I cannot edit this post because it is mostly code, it asked me to add more details. And because there are no more details in my mind to add, I decided to add this text. I'm sorry...
I have a page that has about 4 different forms nested inside of their own modal pop up windows.
I have only ever done one page per form until this point.
What happens is when I submit to the controller and my modelState is not valid is that it routs back to my partial view.
How can I make the #validationFor messages show up in my modal, just like on my single form pages? Basically the page doesn't change, but if it does, it returns the page with the modal still up.
Will this require that I use some sort of Ajax call instead? are there any examples on here of that? I am pretty sure this is probably a duplicate thread, however I couldn't find any resources for myself.
Yes this will require some javascript to make this work.
create partial views to hold a form.
#using (Html.BeginForm("CreateHighSchoolType", "HighSchool", FormMethod.Post, new { id = "CreateHighSchoolTypeForm" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend></legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Create" id="CreateHighSchoolTypeButton" />
</p>
</fieldset>
}
create a controller methods to handle the partial view
public ActionResult CreateHighSchoolTypePartial()
{
return PartialView();
}
[HttpPost]
public ActionResult CreateHighSchoolTypePartial(LookupEditModel viewModel)
{
if (!ModelState.IsValid)
{
return PartialView(viewModel);
}
var hsType = (from t in _highSchoolTypeRepository.FindAll()
where t.Name == viewModel.Name
select t).FirstOrDefault();
if (hsType != null)
{
ModelState.AddModelError("Name", String.Format("This high school type already exists.", viewModel.Name));
return PartialView(viewModel);
}
_highSchoolTypeRepository.Save(new HighSchoolType
{
Name = viewModel.Name
});
return PartialView();
}
wire up everything with some jquery
the dialog opening
$("#AddHighSchoolTypeDialog").dialog({
position: 'center',
autoOpen: false,
modal: true,
resizable: false,
draggable: false,
title: "Add a new high school type",
open: function (event, ui) {
//Load the CreateAlbumPartial action which will return
// the partial view _CreateAlbumPartial
$(this).load("/HighSchoolType/CreateHighSchoolTypePartial", function () {
$("form#CreateHighSchoolTypeForm input:first").focus();
});
},
focus: function (event, ui) {
},
close: function (event, ui) {
$.ajax({
url: "/HighSchoolType/GetAllHighSchoolTypes",
type: "GET",
success: function (data) {
$("#HighSchoolTypeId option").remove();
$.each(data, function (key, value) {
$('#HighSchoolTypeId')
.append($("<option></option>")
.attr("value", key)
.text(value));
});
}
});
//$("#AddHighSchoolTypeDialog").remove();
},
width: 600
});
and the post
$("#CreateHighSchoolTypeButton").live('click', function () {
$.ajax({
url: "/HighSchoolType/CreateHighSchoolTypePartial",
type: "POST",
data: $("#CreateHighSchoolTypeForm").serialize(),
error: function (data) {
var errorMessage = $.parseJSON(data.responseText);
},
success: function (data) {
if (data) {
$('#AddHighSchoolTypeDialog').html(data);
}
else {
$('#AddHighSchoolTypeDialog').html('no data');
}
}
});
return false;
});
Note how on success your need to replace the html of the modal with what was returned from the post call.
As per my understanding, what you are doing is trying to have multiple forms in one page. You can try something like this -
#using (Html.BeginForm("Login", "Member", FormMethod.Post, new {})) {
#Html.LabelFor(m => m.LoginUsername)
#Html.TextBoxFor(m => m.LoginUsername)
#Html.LabelFor(m => m.LoginPassword)
#Html.TextBoxFor(m => m.LoginPassword)
<input type='Submit' value='Login' />
}
#using (Html.BeginForm("Register", "Member", FormMethod.Post, new {})) {
#Html.LabelFor(m => m.RegisterFirstName)
#Html.TextBoxFor(m => m.RegisterFirstName)
#Html.LabelFor(m => m.RegisterLastName)
#Html.TextBoxFor(m => m.RegisterLastName)
#Html.LabelFor(m => m.RegisterUsername)
#Html.TextBoxFor(m => m.RegisterUsername)
#Html.LabelFor(m => m.RegisterPassword)
#Html.TextBoxFor(m => m.RegisterPassword)
<input type='Submit' value='Register' />
}
inside a single page.
You can check answers on this link -
asp.net MVC 4 multiple post via different forms
The unobtrusive validation will only work on forms that exist on page load. if you are loading the pages dynamical in the modals you need to register them after they load
$.validator.unobtrusive.parse("#formid")
or you can just hide the views on load and show when needed
Our security team asked me to not submit plain text passwords in my log in page, we use HTTPS though. so I thought that I need to do client side encryption before submit, I searched for solution and decided to implement jCryption.
However the example presented there is PHP/python, after a few google found this. I did whatever was explained in the link but I don't know how to retrieve form data that user submitted in form.
I only see a key returns in my login post back action and the LoginModel that should contain username, password is null.
Login.cshtml
#model Portal.Model.Membership.LoginModel
#using jCryption
#{
Layout = null;
jCryption.HandleRequest(Request);
}
<html>
<head>
<script src="~/Assets/global/plugins/jquery.min.js" type="text/javascript"></script>
<script src="~/Assets/global/plugins/jquery-migrate.min.js" type="text/javascript"></script>
#jCryption.RenderScriptFor("form", src: #Url.Content("~/Assets/admin/scripts/jquery.jcryption.3.1.0.js"))
</head>
<body>
#using (Html.BeginForm(null, null, FormMethod.Post, new { #autocomplete = "off" }))
{
<div class="form-body">
<div class="form-group">
#Html.LabelFor(x => x.Username, new { #class = "placeholder" })
#Html.TextBoxFor(x => x.Username, new { #class = "form-input", autocomplete = "off" })
<span></span>
</div>
<div class="form-group">
#Html.LabelFor(x => x.Password, new { #class = "placeholder" })
#Html.PasswordFor(x => x.Password, new { #class = "form-input", autocomplete = "off" })
<span></span>
</div>
</div>
<div class="form-group">
<button id="btnLogin">Login</button>
</div>
}
</body>
<!-- END BODY -->
</html>
Update
I put break point on login post action and it popup twice, one with key and another with jCryption:
For MVC 5, you need to adjust a little bit.
at login.cshtml
#using jCryption
#{
jCryption.HandleRequest(Request);
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script src="/Scripts/jquery.jcryption.3.1.0.mod.js"></script>
<script type="text/javascript">
// tweak for compatibility with jquery.validate
(function($){
var _jCryption = $.jCryption;
var jCryptionMod = function(el,options){
var form = $(el), hasValidator = !!form.data('validator');
if (hasValidator){
var v = form.validate();
var prev_handler = v.settings.submitHandler;
v.settings.submitHandler = function (_form, event) {
if( prev_handler ) prev_handler.apply(this, arguments);
var form = $(_form);
if (!form.hasClass('jc-before-submit')) {
v.settings.submitHandler = prev_handler;
form.addClass('jc-before-submit');
setTimeout( function(){ form.trigger('_jc_submit', event); }, 100 );
}
};
_jCryption.call(this, form, $.extend(options, {
submitElement: form,
submitEvent: '_jc_submit',
beforeEncryption: function(){
form.removeAttr('disabled');// form element hack ( IE11 )
return true;
}
}));
} else {
return _jCryption.call(this,el,options);
}
}
$.extend(jCryptionMod, $.jCryption);
$.jCryption = jCryptionMod;
})(jQuery);
</script>
<script type="text/javascript">
$(document).ready(function(){
var form = $('form');
var url = form.attr('action') || '/Account/Login';
form.jCryption({
getKeysURL: url + '?getPublicKey=true',
handshakeURL: url + '?handshake=true'
});
});
</script>
}
AccountController, you should follow JakeJP's documentation (exact same code).
At IE F12 Developer Tools (Network-->go to detail view-->Request body), it showns &jCryptionKey= but not &UserName= and &Password=.
You lack the jCryptionHandler attribute in your action method. The attribute is responsible for handling the jCryption handshake and decryption.
[jCryptionHandler]
public ActionResult Login(LoginModel model)
{
return View();
}
I have the following code inside my ASP.NET MVC 4 razor view:
<div>
<span class="f">Old Tag</span>
#Html.TextBoxFor(model => model.olfTag, new { data_autocomplete_source = Url.Action("AutoComplete", "Home") })
#Html.ValidationMessageFor(model => model.olfTag)
</div>
But data_autocomplete_source with TextBoxFor will not work. Can anyone give me some advice?
The following javascript should add the jquery autocomplete components to your control (and any control with the data-autocomplete-source attribute).
$(function() {
var createAutoComplete = function() {
var $input = $(this);
var options = {
source: $input.attr("data-autocomplete-source")
};
$input.autocomplete(options);
};
$("input[data-autocomplete-source]").each(createAutoComplete);
});
I am working on a ASP.NET MVC web site which has a form that allows for the upload of files using the multipart/form data enctype option on the form tag like so
<form enctype="multipart/form-data" method="post" action='<%= Url.Action("Post","Entries",new {id=ViewData.Model.MemberDetermination.DeterminationMemberID}) %>'>
How would I write this to do an ASP.NET MVC Ajax form post instead?
It is possible but it's a long way.
Step 1: write your form
ex:
#using (Ajax.BeginForm(YourMethod, YourController, new { id= Model.Id }, new AjaxOptions {//needed options }, new { enctype = "multipart/form-data" }))
{
<input type="file" id="image" name="image" />
<input type="submit" value="Modify" />
}
Step 2: intercept the request and send it to the server
<script type="text/javascript">
$(function() {
$("#form0").submit(function(event) {
var dataString;
event.preventDefault();
var action = $("#form0").attr("action");
if ($("#form0").attr("enctype") == "multipart/form-data") {
//this only works in some browsers.
//purpose? to submit files over ajax. because screw iframes.
//also, we need to call .get(0) on the jQuery element to turn it into a regular DOM element so that FormData can use it.
dataString = new FormData($("#form0").get(0));
contentType = false;
processData = false;
} else {
// regular form, do your own thing if you need it
}
$.ajax({
type: "POST",
url: action,
data: dataString,
dataType: "json", //change to your own, else read my note above on enabling the JsonValueProviderFactory in MVC
contentType: contentType,
processData: processData,
success: function(data) {
//BTW, data is one of the worst names you can make for a variable
//handleSuccessFunctionHERE(data);
},
error: function(jqXHR, textStatus, errorThrown) {
//do your own thing
alert("fail");
}
});
}); //end .submit()
});
</script>
Step 3: Because you make an ajax call you probably want to replace some image or something of multipart/form-data
ex:
handleSuccessFunctionHERE(data)
{
$.ajax({
type: "GET",
url: "/Profile/GetImageModified",
data: {},
dataType: "text",
success: function (MSG) {
$("#imageUploaded").attr("src", "data:image/gif;base64,"+msg);
},
error: function (msg) {
alert(msg);
}
});
}
The MSG variable is an base64 encrypted string. In my case it's the source of the image.
In this way I managed to change a profile picture and after that the picture is immediately updated.
Also make sure you add in Application_Start (global.asax)
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
Pretty nice no?
P.S.: This Solution works so don't hesitate to ask more details.
I came across this little hack, which resolves it nicely
window.addEventListener("submit", function (e) {
var form = e.target;
if (form.getAttribute("enctype") === "multipart/form-data") {
if (form.dataset.ajax) {
e.preventDefault();
e.stopImmediatePropagation();
var xhr = new XMLHttpRequest();
xhr.open(form.method, form.action);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
if (form.dataset.ajaxUpdate) {
var updateTarget = document.querySelector(form.dataset.ajaxUpdate);
if (updateTarget) {
updateTarget.innerHTML = xhr.responseText;
}
}
}
};
xhr.send(new FormData(form));
}
}
}, true);
You can use some additional uploaders (e.g. jQuery multiple file uploader) (I prefer this way and I prefer not to use MS Ajax)
Use:
AjaxHelper.BeginForm("Post", "Entries", new {id=ViewData.Model.MemberDetermination.DeterminationMemberID}, new AjaxOptions(){/*some options*/}, new {enctype="multipart/form-data"})
But in second case I'm not sure that it will work.
The jquery forms plugin supports file uploads in this way.
Code which I used and it works !! It's a copy of #James 'Fluffy' Burton solution. I just improvising his answer so that people who is new to MVC will be able to quickly understand the consequences.
Following are my View:
#using (Ajax.BeginForm("FileUploader", null, new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "AjaxUpdatePanel" }, new { enctype = "multipart/form-data", id = "frmUploader" })){
<div id="AjaxUpdatePanel">
<div class="form-group">
<input type="file" id="dataFile" name="upload" />
</div>
<div class="form-group">
<input type="submit" value="Upload" class="btn btn-default" id="btnUpload"/>
</div>
</div>}
<script>
window.addEventListener("submit", function (e) {
var form = e.target;
if (form.getAttribute("enctype") === "multipart/form-data") {
if (form.dataset.ajax) {
e.preventDefault();
e.stopImmediatePropagation();
var xhr = new XMLHttpRequest();
xhr.open(form.method, form.action);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
if (form.dataset.ajaxUpdate) {
var updateTarget = document.querySelector(form.dataset.ajaxUpdate);
if (updateTarget) {
updateTarget.innerHTML = xhr.responseText;
}
}
}
};
xhr.send(new FormData(form));
}
}
}, true);
Following are my controller:
[HttpPost]
public JsonResult FileUploader(HttpPostedFileBase upload)
{
if (ModelState.IsValid)
{
if (upload != null && upload.ContentLength > 0)
{
if (upload.FileName.EndsWith(".csv"))
{
Stream stream = upload.InputStream;
DataTable csvTable = new DataTable();
using (CsvReader csvReader = new CsvReader(new StreamReader(stream), true))
{
csvTable.Load(csvReader);
}
}
else
{
return Json(new { dataerror = true, errormsg = "This file format is not supported" });
}
}
else
{
return Json(new { dataerror = true, errormsg = "Please Upload Your file" });
}
}
return Json(new { result = true });
}
Following is the quick Note of above code:
Through Ajax, I have posted my excel (*.csv) file to Server and read it to an DataTable using a Nuget package (LumenWorksCsvReader).
Hurray! It works. Thanks #James
I actually answered the question myself...
<% using (Ajax.BeginForm("Post", "Entries", new { id = ViewData.Model.MemberDetermination.DeterminationMemberID }, new AjaxOptions { UpdateTargetId = "dc_goal_placeholder" }, new { enctype = "multipart/form-data" }))
For those who still have problems using #Ajax.BeginForm for multipart enctypes / file uploads in MVC
Diagnosis and proposed solution
Running the “Inspect element” tool on a form element generated by the #Ajax.BeginForm helper reveals that the helper, rather inexplicably, overrides the controller parameter specified. This is the case if you implemented a separate controller for your partial postback.
A quick-fix for the problem is to explicitly specify your html action attribute value as /<yourcontrollername>/<youractionname>.
Example
#using (Ajax.BeginForm("", "", new AjaxOptions() { HttpMethod = "POST", UpdateTargetId = "<TargetElementId>", InsertionMode = InsertionMode.Replace }, new { enctype = "multipart/form-data", action = "/<Controller>/<Action>" }))
If you need to use the OnSuccess AjaxOption and/or use Request.IsAjaxRequest() in the controller to check the request type i.e.
#using (Ajax.BeginForm("FileUploader", null, new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "elementToUpdate", OnSuccess = "mySuccessFuntion(returnedData)", OnFailure = "myFailureFuntion(returnedData)"}, new { enctype = "multipart/form-data" }))
Then you can use the following code (I've modified #James 'Fluffy' Burton's answer). This will also convert the response text to JSON object if it can (you can omit this if you want).
<script>
if(typeof window.FormData === 'undefined') {
alert("This browser doesn't support HTML5 file uploads!");
}
window.addEventListener("submit", function (e) {
var form = e.target;
if (form.getAttribute("enctype") === "multipart/form-data") {
if (form.dataset.ajax) {
e.preventDefault();
e.stopImmediatePropagation();
var xhr = new XMLHttpRequest();
xhr.open(form.method, form.action);
xhr.setRequestHeader("x-Requested-With", "XMLHttpRequest"); // this allows 'Request.IsAjaxRequest()' to work in the controller code
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
var returnedData; //this variable needs to be named the same as the parameter in the function call specified for the AjaxOptions.OnSuccess
try {
returnedData = JSON.parse(xhr.responseText); //I also want my returned data to be parsed if it is a JSON object
}catch(e){
returnedData = xhr.responseText;
}
if (form.dataset.ajaxSuccess) {
eval(form.dataset.ajaxSuccess); //converts function text to real function and executes (not very safe though)
}
else if (form.dataset.ajaxFailure) {
eval(form.dataset.ajaxFailure);
}
if (form.dataset.ajaxUpdate) {
var updateTarget = document.querySelector(form.dataset.ajaxUpdate);
if (updateTarget) {
updateTarget.innerHTML = data;
}
}
}
};
xhr.send(new FormData(form));
}
}
}, true);
</script>
N.B. I use the javascript function eval() to convert the string in to a function... if anyone has a better solution please comment.
I also use JQuery JSON.parse() so this isn't a vanilla javascript solution but it isn't required for the script to function so it could be removed.
I mixed Brad Larson answer with Amirhossein Mehrvarzi, because Brad answer wasn't providing any way to handle the response and Amirhossein was causing 2 postbacks.
I just added ($('#formBacklink').valid()) to call model validation before send.
window.addEventListener("submit", function (e) {
if ($('#formBacklink').valid()) {
var form = e.target;
if (form.getAttribute("enctype") === "multipart/form-data") {
if (form.dataset.ajax) {
e.preventDefault();
e.stopImmediatePropagation();
var dataString;
event.preventDefault();
var action = $("#formBacklink").attr("action");
if ($("#formBacklink").attr("enctype") == "multipart/form-data") {
//this only works in some browsers.
//purpose? to submit files over ajax. because screw iframes.
//also, we need to call .get(0) on the jQuery element to turn it into a regular DOM element so that FormData can use it.
dataString = new FormData($("#formBacklink").get(0));
contentType = false;
processData = false;
} else {
// regular form, do your own thing if you need it
}
$.ajax({
type: "POST",
url: action,
data: dataString,
dataType: "json", //change to your own, else read my note above on enabling the JsonValueProviderFactory in MVC
contentType: contentType,
processData: processData,
success: function (data) {
//BTW, data is one of the worst names you can make for a variable
//handleSuccessFunctionHERE(data);
},
error: function (jqXHR, textStatus, errorThrown) {
//do your own thing
}
});
}
}
}
}, true);
Ajax.BegineForm() works with multipart form data and here's the working code example for the same:
View:
#using(Ajax.BeginForm("UploadFile","MyPOC",
new AjaxOptions {
HttpMethod = "POST"
},
new
{
enctype = "multipart/form-data"
}))
{
<input type="file" name="files" id="fileUploaderControl" />
<input type="submit" value="Upload" id="btnFileUpload" />
}
Controller Action Method:
public void UploadFile(IEnumerable<HttpPostedFileBase> files)
{
HttpPostedFileBase file = files.FirstOrDefault(); //Attach a debugger here and check whether you are getting your file on server side or null.
if (file != null && file.ContentLength > 0)
{
//Do other validations before saving the file
//Save File
file.SaveAs(path);
}
}
P.S. Make sure the "name" attribute of the file uploader control and the name of the parameter passed to Action method UploadFile() has to be same (i.e. "files" in this case).
From my little investigation. All the answers above seems to be correct depending on the problem one is having with the Ajax.BeginForm. However, I have just observe that the problem is with the ~/Scripts/jquery.unobtrusive-ajax.min.js javascript library in some case. So in my case I just removed it from the view model and sort of decided to use JQuery Form plugin for my required need along with the HTML Form instead. This has been suggested above.
You can use this code instead of eval
var body = "function(a){ " + form.dataset.ajaxSuccess + "(a) }";
var wrap = s => "{ return " + body + " };"
var func = new Function(wrap(body));
func.call(null).call(null, returnedData);