MVC 3 Image field validation - c#

I have a form that I upload two images. I want to do validation on these images, such as image size and I want to be able to check if the image field are not left blank.
public ActionResult Create(NewsViewModel newsViewModel, IEnumerable<HttpPostedFileBase> files)
{
try
{
//more code here
var originalFile = string.Empty;
IList<string> images = new List<string>(2);
foreach (var file in files)
{
if (file != null && file.ContentLength > 0)
{
var fileName = Path.GetFileName(file.FileName);
if (fileName != null) originalFile = Path.Combine(Server.MapPath(upload_path), DateTime.Now.Ticks+"_ "+ fileName);
file.SaveAs(originalFile);
images.Add(originalFile);
}
}
if (images.Count == 2)
{
newsViewModel.News.Thumbnail = images[0] ?? "";
newsViewModel.News.Image = images[1] ?? "";
}
//more code here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
How can i send response back to the form after checking the image sizes and find out that they are not valid?
or if images.count is not 2, how do i validate that?
any ideas ?

You could add an error to the ModelState and then re-show the same view, something like this:
ModelState.AddModelError(string.Empty, "The image is not valid becuase...");
return View(newsViewModel)
Then in the view if you have a ValidationSummary your validation error message would be shown on it (the first argument is the "key" which matches to the control ID to show the message next to usually, which is why it is String.empty here but maybe you have a control you want it to be associated with).

Related

C#, ASP.NET MVC: Multiple image upload and uploaded images all remain the same problem

I'm trying to upload multiple images, I managed to upload as many images as I wanted. My only problem is that all the images I chose duplicate by copying the first one. I could not find the error in my codes.
My UploadImage method:
public static string UploadImage(string serverPath, IEnumerable<HttpPostedFileBase> files)
{
Guid uniqueName = Guid.NewGuid();
serverPath = serverPath.Replace("~", string.Empty);
string filePath;
foreach (HttpPostedFileBase item in files)
{
if (files != null && item.ContentLength > 0)
{
string extension = Path.GetExtension(item.FileName);
string fileName = $"{uniqueName}{extension}";
if (extension.ToLower() == ".jpeg" || extension.ToLower() == ".gif" || extension.ToLower() == ".png" || extension.ToLower() == ".jpg")
{
if (File.Exists(HttpContext.Current.Server.MapPath(serverPath + fileName)))
{
return "Already exists from same file";
}
else
{
filePath = Path.Combine(HttpContext.Current.Server.MapPath(serverPath),fileName);
item.SaveAs(filePath);
return serverPath+fileName;
}
}
else
{
return "Not the selected picture.";
}
}
else
{
return "No File Selected";
}
}
return "";
}
This is the ImageController:
[HttpPost]
public ActionResult Index(TestClass model, IEnumerable<HttpPostedFileBase> files)
{
foreach (HttpPostedFileBase item in files)
{
model.ImagePath = ImageUploader.UploadImage("~/Images/", files) ;
db.TestClass.Add(model);
db.SaveChanges();
}
return View();
}
and the ImageClass:
public class TestClass
{
public int ID { get; set; }
public string Name { get; set; }
public string ImagePath { get; set; }
}
and the view Index.cshtml:
#using (Html.BeginForm("Index","Image",FormMethod.Post, new { enctype="multipart/form-data"}))
{
<div>
Name
</div>
<div>
#Html.TextBoxFor(x=>x.Name)
</div>
<div>
<input multiple type="file" name="files" value="Browse" />
</div>
<div>
<button class="btn btn-primary">Save</button>
</div>
}
Your problem is that you just once generate a uniquename and use it for all of them.
you should put this line :
Guid uniqueName = Guid.NewGuid();
in you for loop, like this :
foreach (HttpPostedFileBase item in files)
{
if (files != null && item.ContentLength > 0)
{
Guid uniqueName = Guid.NewGuid();
string extension = Path.GetExtension(item.FileName);
string fileName = $"{uniqueName}{extension}";
// the rest of your code
}
and also there are some more things to fix
1- in you action method, you upload all files but your UploadImage method just returns a string, so ImagePath for all of them are equal.
you can rewrite it like below:
foreach (HttpPostedFileBase item in files)
{
var fileObj = new TestClass();
fileObj.ImagePath = ImageUploader.UploadImage("~/Images/", files) ;
db.TestClass.Add(fileObj);
db.SaveChanges();
}
Thank you for your help, but writing these codes didn't solve my problem.
Let me explain the problem by giving an example;
For example, I select four pictures and when I click the save button, it adds to the database with the new Guid name, but the file extension of the first selected photo remains in the memory. Four pictures are added to the images folder, but the images are all the same.
enter image description here
enter image description here
SOLVED
I realized that the reason that the photos I tried to upload were uploaded as the same photo all the time was because the index number was always reset.
As a solution: I removed the foreach loop from the ImageUploader static method and defined an int type variable to the static class, and each time the foreach loop in ImageController runs, it will necessarily run on the static method and will increment once each time. When we want to upload an image again, we return it to zero when the process is finished so that the index number does not remain the same. This solution can be used when we both upload multiple photos and save data to the database. If we only want to upload multiple photos, a foreach loop should be used.
public static class ImageUploader
{
internal static int indexer;
public static string UploadImage(string serverPath, List<HttpPostedFileBase> files)
{
serverPath = serverPath.Replace("~", string.Empty);
if (files != null || files[indexer].ContentLength < 0)
{
Guid uniqueName = Guid.NewGuid();
string extension = Path.GetExtension(files[indexer].FileName);
string fileName = $"{uniqueName}{extension}";
if (extension.ToLower() == ".jpeg" || extension.ToLower() == ".gif" || extension.ToLower() == ".png" || extension.ToLower() == ".jpg")
{
if (File.Exists(HttpContext.Current.Server.MapPath(serverPath + fileName)))
{
return "Already Exists From Same File";
}
else
{
string filePath = Path.Combine(HttpContext.Current.Server.MapPath(serverPath),fileName);
files[indexer].SaveAs(filePath);
indexer++;
if (indexer == files.Count)
{
indexer = 0;
}
return serverPath + fileName;
}
}
else
{
return "Not the selected picture.";
}
}
else
{
return "No File Selected";
}
}
}
}

C# use field 1 value if field 2 empty

I am running through a set of records using a for each loop, and also doing simple checks to ensure that good data is inserted into a database table.
Sometimes the dataset can be missing the LegistarID value, the change I need to do in my code, is to add a check for LegistarItem,
if the value of LegistarID is missing, but the AgendaItem value is not, then assign the value of AgendaItem to LegistarID
if LegistarId is missing, and there is also no AgendaItem value, then return a message to the user, to let them know that these values need to be present in the dataset they are trying to import.
I know it does not sound complex, but I am having a hard time making this change successfully. I need a bit of help if possible, please.
Here is my code as I currently have it:
if (ModelState.IsValid)
{
using (Etities db = new Entities())
{
foreach (var i in meeting)
{
if (i.MeetingID == 0)
{
message = string.Format("This file is missing the Meeting ID value of at least 1 record. \n Verify that the data you are trying to upload meets the criteria, and then try to upload your file again.", i.MeetingID);
return new JsonResult { Data = new { status = status, message = message } };
}
else
{
// development
var compositeKey = db.MeetingAgenda.Find(i.MeetingID, i.AgendaItem);
if (compositeKey == null)
{
// Add new
// development
db.MeetingAgenda.Add(i);
//
}
else
{
// Serves as an update, or addition of a previously imported dataset
db.Entry(compositeKey).CurrentValues.SetValues(i.MeetingID);
db.Entry(compositeKey).State = EntityState.Modified;
}
}
}
db.SaveChanges();
status = true;
}
}
else
{
message = string.Format("Please, verify that the file you are trying to upload is correctly formatted, and that the data it contains, meets the expected criteria, then click the upload button again. \n Thank you!");
return new JsonResult { Data = new { status = status, message = message } };
}
I think that part of the code I need is something like this:
else if (i.LegistarID == 0 and i.AgendaItem != 0)
{
i.LegistarID = i.AgendaItem
}
I just am unsure how in the current code place it.
I would check all rows before returning a result.
if (ModelState.IsValid) {
var errors = new List<string> ();
var rowCounter = 1;
using (Etities db = new Entities ()) {
foreach (var i in meeting) {
if (i.MeetingID == 0) {
// Let the user know this row is bad
errors.Add ($"Row {rowCounter}: This file is missing the Meeting ID. Verify that the data you are trying to upload meets the criteria, and then try to upload your file again.");
}
// Check if LegistarID is missing
if (i.LegistarID == 0) {
// Check if Agenda Item is present
if (i.AgendaItem == 0) {
errors.Add ($"Row {rowCounter}: Meeting has no LegistarID and no Agenda Item. Please check data");
} else {
i.LegistarID = i.AgendaItem
}
}
// development
var compositeKey = db.MeetingAgenda.Find (i.MeetingID, i.AgendaItem);
if (compositeKey == null) {
// Add new
// development
db.MeetingAgenda.Add (i);
//
} else {
// Serves as an update, or addition of a previously imported dataset
db.Entry (compositeKey).CurrentValues.SetValues (i.MeetingID);
db.Entry (compositeKey).State = EntityState.Modified;
}
rowCounter++;
}
// If there are errors do not save and return error message
if (errors.Count > 0) {
return new JsonResult { Data = new { status = false, message = string.Join ("\n", errors) } };
}
db.SaveChanges ();
status = true;
}
} else {
message = string.Format ("Please, verify that the file you are trying to upload is correctly formatted, and that the data it contains, meets the expected criteria, then click the upload button again. \n Thank you!");
return new JsonResult { Data = new { status = status, message = message } };
}
The "if(i.MeetingID == 0)" else is redundant, because you are returning if the condition is met. So to avoid unneeded/confusing nesting I would rewrite the actual code (of the loop only) as:
foreach (var i in meeting)
{
if (i.MeetingID == 0)
{
message = string.Format("This file is missing the Meeting ID value of at least 1 record. \n Verify that the data you are trying to upload meets the criteria, and then try to upload your file again.", i.MeetingID);
return new JsonResult { Data = new { status = status, message = message } };
}
// development
var compositeKey = db.MeetingAgenda.Find(i.MeetingID, i.AgendaItem);
if (compositeKey == null)
{
// Add new
// development
db.MeetingAgenda.Add(i);
//
}
else
{
// Serves as an update, or addition of a previously imported dataset
db.Entry(compositeKey).CurrentValues.SetValues(i.MeetingID);
db.Entry(compositeKey).State = EntityState.Modified;
}
}
Then, I would add the new condition in between the MeetingID = 0 check and the rest of the code, like this:
foreach (var i in meeting)
{
if (i.MeetingID == 0)
{
message = string.Format("This file is missing the Meeting ID value of at least 1 record. \n Verify that the data you are trying to upload meets the criteria, and then try to upload your file again.", i.MeetingID);
return new JsonResult { Data = new { status = status, message = message } };
}
// *** New check on LegistarID and AgendaItem ***
if(i.LegistarID == 0)
{
// Is there a chance to fill LegistarID with AgendaItem?
if(i.AgendaItem != 0)
{
// Yes, fill it and then let the rest of the code flow peacefully.
i.LegistarID = i.AgendaItem
}
else
{
// No way: I must stop the procedure here and warn the user about this.
// return "these values need to be present in the dataset they are trying to import."
}
}
// development
var compositeKey = db.MeetingAgenda.Find(i.MeetingID, i.AgendaItem);
if (compositeKey == null)
{
// Add new
// development
db.MeetingAgenda.Add(i);
//
}
else
{
// Serves as an update, or addition of a previously imported dataset
db.Entry(compositeKey).CurrentValues.SetValues(i.MeetingID);
db.Entry(compositeKey).State = EntityState.Modified;
}
}

How to pass a file to a controller

I have this controller :
public ActionResult Index(HttpPostedFileBase file)
{
if (file != null && file.ContentLength > 0)
try
{
string path = Path.Combine(Server.MapPath("~/Files"),
Path.GetFileName(file.FileName));
file.SaveAs(path);
ViewBag.Message = "Success";
}
catch (Exception ex)
{
ViewBag.Message = "Error:" + ex.Message.ToString();
}
return RedirectToAction("NewController", new { myFile : file });
}
My new controller :
public ActionResult NewController(HttpPostedFile myFile)
{
}
I want to pass "file" to the NewController but it gives me an error at RedirectToAction. How can I pass correct values to RedirectToAction so that it will work? Thanks.
The File is potentially very complex object and you can't pass potentially complex object in simple RedirectToAction. So you have to store File in Session to get it in your next redirection but storing data in Session is not good due to performance perspective and you have to set Session null after retrieving of data from it.
But you can use TempData instead which remains alive during subsequent requests and it immediately destroyed after you retrieved data from it.
So just add your file in TempData and retrieve it in New Controller Action.
Another thing that i noticed that you are storing Message in ViewBag. But ViewBag becomes null during redirection, so you won't be able to get ViewBag.Message in your NewControllerAction action. To make it accessible in your NewControllerAction, you have to store it in TempData but Message is going to have simple string so you can pass it as parameter to NewControllerAction action.
public ActionResult Index(HttpPostedFileBase file)
{
string Message = string.Empty;
if (file != null && file.ContentLength > 0)
try
{
string path = Path.Combine(Server.MapPath("~/Files"), Path.GetFileName(file.FileName));
file.SaveAs(path);
Message = "Success";
}
catch (Exception ex)
{
Message = "Error:" + ex.Message.ToString();
}
//Adding File in TempData.
TempData["FileData"] = file;
return RedirectToAction("NewControllerAction", "NewController", new { strMessage = Message });
}
In your new controller:
public ActionResult NewControllerAction(string strMessage)
{
if(!string.IsNullOrWhiteSpace(strMessage) && strMessage.Equals("Success"))
{
HttpPostedFileBase myFile = TempData["FileData"] as HttpPostedFileBase;
}
else
{
//Something went wrong.
}
}

Display error message if variable isn't what i expect

I'm receiving List<int> percentage as parameter in POST controller.
I'm doing that:
var prc = 0;
var prcCount = 0;
foreach (var pr in percentage)
{
prc += pr;
prcCount++;
}
if (prc != 100)
return View();
Now I want that instead of return View(); it display error message that percentage must be 100. How can I do that?
add message in viewbag
if (prc != 100)
{
ViewBag.PercentageMessage = "your error message."
return View();
}
and in view check if ViewBag.PercentageMessage is not null and empty then display message in label.
if (ViewBag.PercentageMessage != null)
{
string message = Convert.ToString(ViewBag.PercentageMessage);
if(message != "")
{
<label>#message</label>
}
}
put this code where you want to display message
Assuming the return type is ActionResult
return Content("Percentage must be 100");
string Percentage = "Percentage must be 100";
if (prc != 100)
return Json(Percentage);
Put message in ViewBag, ViewData or model and diplay it with jquery.
Something like this
ViewBag.Error = "percentage must be 100";
javascript view
var ErrorMessage = #ViewBag.Error;
jquery
if (ErrorMessage.length>0) {
$("#divError").show().html(ErrorMessage);
}

CheckBoxList in a complex view

I have searched here many times but I could not find what I want.
I am developing an application where I have USERS with specific Skills, and I want to relate them to specific project.
So I have the following tables: Users, UserSkills and more
My question is: I am using CRUD in MVC4, and when I open the EDIT view from the UserDetail Controller, in order to edit the user information, I need also to add (in the same Edit view) partial view, or any mechanism, where I list the user skills, using CheckBoxes to help in multi-selecting various skills for this user, and then when pressing "Save" it should store the User and UserSkills information back to the dB (MS-SQL).
I am using this Model:
public class SkillsViewModel
{
public IList<Skill> AvailableSkills { get; set; }
public IList<Skill> SelectedSkills { get; set; }
public SavedSkills SevedSkills { get; set; }
public User Usr { get; set; }
}
SavedSkills are the UserSkills Table, which will be used for the dB
AvailableSkills are the Skills Table
SelectedSkills are the ones that are selected in the Edit view
Keeping in mind that the Edit view also contain an image upload file:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(User Usr, HttpPostedFileBase file)
{
#region//validate file is selected
if (file != null)
{
if (file.ContentLength > (512 * 1000)) // 512 KB
{
ModelState.AddModelError("FileErrorMessage", "File size
must be within 512KB");
}
string[] allowedType = new string
[]"image/png", "image/gif",
"image/jpg", "image/jpeg" };
bool isFileTypeValid = false;
foreach (var i in allowedType)
{
if (file.ContentType == i.ToString())
{
isFileTypeValid = true;
break;
}
}
if (!isFileTypeValid)
{
ModelState.AddModelError
("FileErrorMessage", "Only .png,
.gif and .jpg file allowed");
}
}
#endregion
if (ModelState.IsValid)
{
if (Skk.Skk.Count (x => x.IsSelected) == 0)
{
//return "You have not selected any City";
}
else
{
StringBuilder sb = new StringBuilder();
sb.Append("You selected - ");
foreach (Skill skilll in Skk.Skk)
{
if (skilll.IsSelected)
{
sb.Append(skilll.SkillName + ", ");
}
}
//sb.Remove(sb.ToString().LastIndexOf(","), 1);
//return sb.ToString();
}
//Update User
if (file != null)
{
string savePath = Server.MapPath("~/Pictures");
string fileName = Guid.NewGuid() + Path.GetExtension
(file.FileName);
file.SaveAs(Path.Combine(savePath, fileName));
Usr.ImagePath = fileName;
}
using (dBEntities dc = new dBEntities())
{
var v = dc.Users.Where(a => a.Id.Equals
(Usr.Id)).FirstOrDefault();
if (v != null)
{
v.UserName = Usr.UserName;
v.Email = Usr.Email ;
v.StartDate = Usr.StartDate ;
v.Company = Usr.Company ;
v.Position = Usr.Position;
v.Division = Usr.Division ;
v.Department = Usr.Department ;
v.PM = Usr.PM ;
v.AM = Usr.AM;
v.Permissions = Usr.Permissions;
v.IsActive = Usr.IsActive;
if (file != null)
{
v.ImagePath = Usr.ImagePath ;
}
}
dc.SaveChanges();
}
return RedirectToAction("Index");
}
ViewBag.Department = new SelectList
(db.Departments, "DepID", "DepName", Usr.Department);
ViewBag.Division = new SelectList
(db.Divisions, "DivID", "DivName", Usr.Division);
ViewBag.Position = new SelectList
(db.Positions, "PosID","PosName", Usr.Position);
return View(Usr);
}
I hope I have explained it well, and thank you in advance.
I had same situation with you.
I was used chosen to add user's skills. IMHO, it more user friendly then using checkboxes (you can look chosen in action in linked-in, when you add your skills) when you have more than 20 skills in your database.
Chosen is regular HTML "select" tag with multiple attribute but more beautiful.
I also use Ajax JQuery to post my data to controller.
JQuery POST method also support send form data with attached file.
So you don't need to change your Edit action a lot. What you need is add parameter for selected skills (it's a string which separated by comma).

Categories