Custom validation in MVC not executing on partial views - c#

So I have file uploading input which is strongly typed and below is the model class
public class UploadImageAlbum
{
[CustomFileValidator]
public HttpPostedFileBase Images { get; set; }
}
and my CustomFileValidator class is as below:
[AttributeUsage(AttributeTargets.Property,AllowMultiple =true,Inherited =false)]
public class CustomFileValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
const int maxContent = 1024 * 1024 * 50;//50 MB
var sAllowedExt = new string[] { ".jpg", ".png" };
var file = value as HttpPostedFileBase;
//Check for null
if (file == null)
{
return new ValidationResult("Please select an image to upload");
}
//Check for File Extension
if (sAllowedExt.ToList().Contains(file.FileName.Substring(file.FileName.LastIndexOf('.'))))
{
return new ValidationResult("Please Upload a valid file of type : " + string.Join(",", sAllowedExt));
}
//Check for length of file
if(file.ContentLength>maxContent)
{
return new ValidationResult("File is too large, maximum allowed size is :" + (maxContent / 1024) + "MB");
}
return ValidationResult.Success;
}
}
and my partialview is as below:
#using (Html.BeginForm("UploadImages", "Admin", FormMethod.Post, htmlAttributes: new { id = "frmUploadImages", novalidate = "novalidate", autocomplete = "off", enctype = "multipart/form-data" }))
{
<div class="form-group">
<span class="btn btn-default btn-file-img">
Browse #Html.TextBoxFor(m => m.UIAModel.Images, new { type = "file", multiple = "multiple", data_charset = "file" })
</span>
<span class="text-muted" id="filePlaceHolder">No files selected</span>
#Html.ValidationMessageFor(m=>m.UIAModel.Images, null, htmlAttributes: new { #class = "invalid" })
</div>
<div class="form-group">
<button class="btn btn-primary addImage pull-right">
<i class="fa fa-upload"></i> Upload
</button>
</div>
}
and below is how I load partialview on click of a link:
$('#page-inner').empty().load('/Admin/GetMediaUploadView', function () {
$.validator.unobtrusive.parse($('form#frmUploadImages'));
//Apply client validation for partialviews
})
But even after following all the steps, it isn't displaying any message and point to note is, if I add [Required] attribute for the same, it will work well, but custom validation what I have never displays any message. What else I have to add to make this CustomValidator to work? I followed this post but still could not be of much help. Also if anyone let me know how to update this model so as to accept multiple images, it will be of great help.

In order to get client side validation, your attribute must
implement IClientValidatable which will add the associated
data-val-* attributes to you html, and
you must include scripts to add methods to the jQuery validator.
This article is a good guide to creating custom client and server side validation attributes.
Note also your current attribute is rather limited in that the file types and size are fixed, and it would be more flexible to include properties to specify the file types and maximum file size so that you could use it as (say)
[FileValidation(MaxSize="1024", FileType="jpg|png")]
public HttpPostedFileBase Images { get; set; }
This article provide an example of an attribute that validates the file type, but could be adapted to include the MaxSize property.
Side note: If your loading dynamic content, then you should first set the validator to null
var form = $('form#frmUploadImages');
form.data('validator', null);
$.validator.unobtrusive.parse(form);

Related

ASP.NET MVC Localizing or Changing Default Model Binding Error Messages

How to change the language for "The value 'some value' is not valid for 'some property'" validation error?
Can anyone help? I want to translate the error in the picture to Russian Error. I read much sites, try to use RegularExpression, but it doesn't help
may be I don't correctly understand how to do that?
I need only translate the error, not need to change the culture.
In web.config:
<globalization culture="en" uiCulture="en" />
My entity with data annotations attributes:
public class Player
{
/* Some other properties */
[Required(ErrorMessage = "Укажите среднее количество блокшотов")]
[Range(0, 10.0, ErrorMessage = "Недопустимое значение, до 10")]
public float BlockPerGame { get; set; }
/* Some other properties */
}
My View:
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.Id)
<div class="box-form">
/* Some other properties */
<div class="text-style-roboto form-group">
<label>Среднее количество блокшотов</label>
#Html.TextBoxFor(m => m.BlockPerGame, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.BlockPerGame)
</div>
/* Some other properties */
<div class="form-group">
<button type="submit" class="button button-create" id="button-create">Добавить</button>
#Html.ActionLink("Отмена", "Index", null, new { #class = "button button-cancel", id = "button-cancel" })
</div>
</div>
}
And my Controller:
public class AdminController : Controller
{
/*Some other methods*/
[HttpPost]
public async Task<ActionResult> Edit(Player player, string ChoosingTeam)
{
if (ModelState.IsValid)
{
if (ChoosingTeam != string.Empty)
{
try
{
player.TeamId = int.Parse(ChoosingTeam);
await repository.SavePlayerAsync(player);
TempData["message"] = string.Format("Игрок {0} {1} сохранены", player.Name, player.Surname);
return RedirectToAction("Index");
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
}
}
IEnumerable<SelectListItem> list = new SelectList(repository.Teams, "Id ", "Name");
ViewBag.ChoosingTeamName = list;
return View(player);
}
}
When you enter an invalid value for a property, if model binder cannot bind that value to the property, the model binder sets an error message for that property. It's different from data-annotations model validation. It's in fact model binder validation error.
Localizing or Changing Default Model Binding Error Messages
Model binding error messages are different from model validation messages. To customize or localize them, you need to create a global resource and register it in Application_Start for DefaultModelBinder.ResourceClassKey.
To do so, follow these steps:
Go to Solution Explorer
Right click on project → Add → ASP.NET Folder → Choose App_GlobalResources
Right click on App_GlobalResources → Choose Add New Item
Choose Resource File and set the name to ErrorMessages.resx
In the resource fields, add the following keys and values and save the file:
PropertyValueInvalid: The value '{0}' is not valid for {1}.
PropertyValueRequired: A value is required.
Note: If you want to just customize the messages, you don't need any language-specific resource, just write custom messages in the ErrorMessages.resx and skip next step.
If you want localization, for each culture, copy the resource file and paste it in the same folder and rename it to ErrorMessages.xx-XX.resx. Instead of xx-XX use the culture identifier, for example fa-IR for Persian language
and enter translation for those messages, for example for ErrorMessages.fa-IR.resx:
PropertyValueInvalid: مقدار '{0}' برای '{1}' معتبر نمی باشد.
PropertyValueRequired: وارد کردن مقدار الزامی است.
Open Global.asax and in Application_Start, paste the code:
DefaultModelBinder.ResourceClassKey = "ErrorMessages";
ASP.NET CORE
For ASP.NET Core read this post: ASP.NET Core Model Binding Error Messages Localization.

Upload pdf file

I want to upload Pdf file into my application
So I have ViewModel (I post only relevant code)
public class SubcategoryViewModel
{
public HttpPostedFileBase PdfFile { get; set; }
[DisplayName("PDF")]
public string Pdf { get; set; }
}
Controller:
public async Task<string> CreateSubcategory(SubcategoryViewModel model)
{
string pdf = null;
if (model.Pdf != null)
{
pdf = Guid.NewGuid().ToString().Substring(0, 13) + "_" + model.File.FileName;
model.Pdf = pdf;
var path = Path.Combine(HttpContext.Current.Server.MapPath("~/Content/pdf"), pdf);
model.File.SaveAs(path);
}
var subcategory = new Subcategory
{
Pdf = pdf,
};
db.SubcategoriesList.Add(subcategory);
await db.SaveChangesAsync();
View:
#model Models.ViewModels.SubcategoryViewModel
#using (Html.BeginForm("Create", "Subcategory", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.LabelFor(model => model.Pdf, new { #class = "control-label col-md-2" })
<div class="col-md-10 legend-size">
<input type="file" id="file" name="file" />
</div>
</div>
When I action post I don´t received nothing into model.Pdf, so into if (model.Pdf != null) validation it comes null, and I don´t know why
Anyone have idea why it occurs? Thankyou in advance!
You have 2 main issues. First the name of your file input is name="file", but that does not match the property in your model. It needs to be
<input type="file" name="PdfFile" />
Second, you never generate an input for property string Pdf so in the POST method, it will always be null and therefore the code inside your if (model.Pdf != null) will never be executed. However, what you really want is to save the file if its not null, so the code needs to be
public async Task<string> CreateSubcategory(SubcategoryViewModel model)
{
string fileName = null;
if (model.PdfFile != null && model.PdfFile .ContentLength > 0)
{
fileName = Guid.NewGuid().ToString().Substring(0, 13) + "_" + model.PdfFile.FileName;
string path = Path.Combine(HttpContext.Current.Server.MapPath("~/Content/pdf"), fileName);
model.PdfFile.SaveAs(path);
}
var subcategory = new Subcategory
{
Pdf = fileName ,
};
db.SubcategoriesList.Add(subcategory);
await db.SaveChangesAsync();
Side note: I would also recommend an additional property in your model for the files display name (i.e. the value of model.PdfFile.FileName) so that you can use that in your view rather than displaying the name prefixed with a Guid which would have little meaning to the user. Refer this answer for an example.
I think your problem is you are getting null value in PdfFile property of your model. This is because you have not given name attribute of file upload control same as you property. change name attribute of your file upload control to PdfFile.
<input type="file" id="file" name="PdfFile" />

How to validate a field is not empty

I have a view as abcd.cshtml with below code
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.id)
#Html.HiddenFor(model => model.createtime)
<h3>Headline</h3>
<div class="editor-field">
#Html.EditorFor(model => model.general)
#Html.ValidationMessageFor(model => model.general)
</div>
<a class="anchor" id="keywords"></a>
<h3>Keywords</h3>
<div class="editor-field">
#Html.EditorFor(model => model.keywords)
#Html.ValidationMessageFor(model => model.keywords)
</div>
<a class="anchor" id="relatedcq"></a>
<h3>Related CQ</h3>
<div class="editor-field">
#Html.EditorFor(model => model.relatedcq)
#Html.ValidationMessageFor(model => model.relatedcq)
</div>
<p>
<input type="submit" value="Create" class="btn btn-primary" />
</p>
}
</div>
The controller is simple abcd.cs, I just put this into a DB
[HttpPost]
[ValidateInput(false)]
public ActionResult Create(staging staging)
{
staging.modifiedby = User.Identity.Name;
staging.lastmodified = DateTime.Now;
staging.createtime = DateTime.Now;
try
{
db.stagings.Add(staging);
db.SaveChanges();
}
catch (InvalidOperationException e)
{
ViewData["error"] = "An error has occured: " + e.Message;
return View(staging);
}
return RedirectToAction("Details/" + staging.id);
}
What I want is to make sure that Keywords is filled. If Keywords is filled I need to have a pop up window saying "Please fill the Keywords".
I tried doing that using MessageBox.Show() but then for that I had to add System.Windows.Forms and that had some conflicts with System.Web.Mvc;
If you're using htmlhelper ValidationMessageFor would'nt you rather display a Validation Summary to the user the shows him/her all the fields that you require filled in?
e.g.
If you have a model and you have multiple fields that need to be filed in, which can be validated by decorating those fields/properties with the [Required] attribute, or any other that you see fit e.g. [StringLength] etc.
If you do that you can provide a validation summary, using the model binder to not post youre data if it doesnt meet the set validation.
Validation summary example:
#Html.ValidationSummary(false, "Please provide the details above and then click submit.")
That above will display all the validation errors of all fields marked with the following e.g. #Html.ValidationMessageFor(model => model.relatedcq)
Image of how the output, if there are validation errors, will be displayed.
Hope this helps :)
To achieve this you need to create a custom validation attribute on server side. So for e.g. Let's name it as MustBeFilled. Your new attribute would look like:
public class MustBeFilledAttribute : ValidationAttribute, IClientValidatable // IClientValidatable for client side Validation
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
return new ValidationResult(FormatErrorMessage(null));
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "mustbefilled",
ErrorMessage = ErrorMessage //Added
};
modelClientValidationRule.ValidationParameters.Add("param", metadata.DisplayName); //Added
return new List<ModelClientValidationRule> { modelClientValidationRule };
}
}
Now you would need a client side script as well to take action if the rule fails. As in your case you want to show a popup. Add below to your site script and make sure it renders after the Jquery-validation-* scripts are rendered.
(function ($) {
$.validator.addMethod('mustbefilled', function (value, element, params) {
if ($(element).val() != '')
return true;
alert('Fill it first.');
// Here you need to Invoke the modal popup.
return false;
}, '');
$.validator.unobtrusive.adapters.add('mustbefilled', ['param'], function (options) {
options.rules["mustbefilled"] = '#' + options.params.requiredif;
options.messages['mustbefilled'] = options.message;
});
})(jQuery);
To apply this custom validation, first add the attribute on your Model property.
[MustBeFilled]
public string Keywords { get; set; }
Then add the custom javascript to bundle.config, only if you have kept the code in separate file named mustbefilled.js. Here I intentionally added the javascript file with validation plugin so you don't get exception if it rendered before the validation plugin.
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate*",
"~/Scripts/mustbefilled.js"));
That's it you're all set.
Now all you have to do is create a model pop and call it in the mustbefilled.js where I placed the comment to invoke it. See this sample here which would help you create a bound modal popup to your keywords property.

HttpPostedFileBase not showing image with bootstrap file upload plugin

I have a view model that has a HttpPostedFileBase property called 'StudentImage'. When the user logs in I want to fetch a byte array (my image from DB) and display it? I can fetch the byte[] from the database, and I can set the byte[] back to my HttpPostedFileBase Image by setting a memory stream that inherits from httppostedfilebase. But no image shows up on my form
Here is my view model
public class EditStudentViewModel
{
...other code here
public HttpPostedFileBase StudentImage { get; set; }
}
Here is my controller where I fetch the byte array and I want to set the byte[] to 'StudentImage', which is a HttpPostedFileBase
public ActionResult Edit()
{
var years = Enumerable.Range(DateTime.Now.Year - 100, 100).Reverse();
string userId = User.Identity.GetUserId();
Student student = studentRepository.Find(userId);
// student.StudentImage => this is a byte array that I need to get
// into the HttpPostedFileBase named StudentImage into
// 'EditStudentViewModel'
var studentViewModel = new EditStudentViewModel
{
...other properties set here
StudentImage = new MemoryFile(new MemoryStream(student.StudentImage.Image));
};
I created a new class called MemoryFile and inherited HttpPostedBaseFIle like so
class MemoryFile : HttpPostedFileBase
{
Stream stream;
public MemoryFile(Stream stream)
{
this.stream = stream;
}
public override Stream InputStream
{
get { return stream; }
}
}
it seems to set the values correctly, but when I view the form on the screen I don't see the image! It doesn't set it with the Bootstrap File Plugin I'm using that can be found here BootStrap File Upload Plugin
Here is my javascript for the file uplaod plugin
$("#input-20").fileinput({
'type': 'POST',
'cache': false,
'browseClass': 'btn btn-primary btn-block',
'showCaption': false,
'showRemove': false,
'showUpload': false,
'uploadAsync': false,
'maxFileCount': 1,
'allowedFileExtensions': ['jpg', 'png', 'gif'],
'allowedFileTypes': ['image'],
//'uploadUrl': '#Url.Action("Edit", "Student")'
});
Here is my HTML tag
<div class="panel-body">
#Html.TextBoxFor(model => model.StudentImage, new { #type = "file", #id = "input-20" })
</div>
You cannot show an image in an <input type="file" />, you must use a <img> tag. When you do #Html.TextBoxFor(x => x, new { type="file" }), the rendered output will be an <input type="file" />, nothing special.
If you need to show the existing image, you should do it something like this:
<div class="panel-body">
<!-- show the current StudentImage in the database -->
<img src="#Url.Action("GetStudentImage", new { studentID= Model.StudentImageID })" />
<!-- for adding a new StudentImage -->
#Html.TextBoxFor(model => model.StudentImage, new { #type = "file", #id = "input-20" })
</div>
Your action in the controller would look like this, based on the links I posted in the comments:
public ActionResult GetStudentImage(int studentImageID)
{
Student student = studentRepository.Find(studentID);
return File(student.StudentImage.Image, "image/jpg");
}

Setting the text-box value as the subject in Mailto functionality.

I have created a custom html helper in MVC for MailTo functionality.
But I got an requirement to set the subject of the mail to the value user have entered in another textbox field.
I am not sure how to achieve this, Can some body please help?
Thanks
Html Helper
public static MvcHtmlString SendMailTo(this HtmlHelper helper, string emailAddress, string subject, string displayText)
{
var sb = string.Format("{3}",
CharEncode("mailto:"), CharEncode(emailAddress),CharEncode("?subject=" +subject), CharEncode(displayText));
return new MvcHtmlString(sb);
}
public static string CharEncode(string value)
{
var enc = System.Text.Encoding.Default;
var retval = "";
for (var i = 0; i < value.Length; i++)
{
retval += "&#" + enc.GetBytes(new[] { Convert.ToChar(value.Substring(i, 1)) })[0] + ";";
}
return retval;
}
View:
<div class="form-group">
#Html.LabelFor(m => m.ApplicationId, new { #class = "col-sm-3 control-label" })
<div class="col-sm-8">
#Html.TextBoxFor(m => m.ApplicationId, new {#class = "form-control"})
</div>
</div>
<div class="form-group">
<div class="col-sm-11" style="text-align:right">
#Html.SendMailTo("info#test.com", "Password Request: ", "Request Password")
<button type="submit" class="button">Sign in</button>
</div>
</div>
You can not get that value without any jquery code.
You should make an event on keyup ( for example ) on your texbox ( this is the one which is used as an user mail input )
Put your email form in a partial view and update that partial when that event is triggered.
INFO :
A) To render your ParialView use can Html.Partial helper -> Example here
B) To get textbox value you can use following jquery code
`$('.class of your textbox').val()' //will return a string
C) To update your partial view
c.1 Place your partial view in a `div element`
c.2 Make an `ajax` call from `jquery` to an action which returns your partial view filled with data
Example for c.2:
*AJAX*
$.ajax({
type:"GET",
data:{
mail:$('.class of your textbox').val()
},
success: function(response){
$(".class of your div").html(response.Html);
}
error: function(response){
// whatever you want to do
}
});
*Controller Action*
public JsonResult(string mail)
{
var model = new CustomModel(){ Mail=mail }; // custom class to your as model for your partial view
var html = RenderPartial(...) // method you can find in the link posted below
return Json(
{
Html=html
},JsonRequestBehavior.AllowGet)
}
Link I was writing about few lines above - follow this link
You can call that ajax on whatever event you want for your textbox.

Categories

Resources