I have an ASP.NET MVC Controller, the relevant parts of which appear here:
public class IncidentController : Controller
{
private IncidentDBContext db = new IncidentDBContext();
public ActionResult Edit(int id = 0)
{
Incident incident = db.Incidents
.Include(i => i.Images)
.Where(i => i.IncidentID == id)
.Single();
if (incident == null)
{
return HttpNotFound();
}
return View(incident);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Incident incident, int[] SelectedImages)
{
if (ModelState.IsValid)
{
UpdateRelatedImages(incident, SelectedImages);
db.Entry(incident).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(incident);
}
private void UpdateRelatedImages(Incident incident, int[] selectedImageIDs)
{
incident.Images = new List<IncidentImage>();
foreach (int imageID in selectedImageIDs)
{
IncidentImage image = db.IncidentImages.Where(i => i.IncidentImageID == imageID).Single();
if (image.IncidentID == incident.IncidentID)
{
incident.Images.Add(image);
}
else if (image.IncidentID == null)
{
incident.Images.Add(image);
image.IncidentID = incident.IncidentID;
db.Entry(image).State = EntityState.Modified;
}
else
{
throw new InvalidOperationException(String.Format("Cannot assign image {0} to incident {1}. Current Incident ID of image: {2}.", imageID, incident.IncidentID, image.IncidentID));
}
}
}
[HttpPost]
public ActionResult UploadImages()
{
var images = new List<object>();
foreach (string imageName in Request.Files.AllKeys)
{
HttpPostedFileBase file = Request.Files[imageName];
string fileName = Guid.NewGuid().ToString() + Path.GetExtension(imageName);
string filePath = Server.MapPath("~/UploadedImages/" + fileName);
file.SaveAs(filePath);
int insertedID = CreateImage(imageName, fileName);
images.Add(new { ImageName = imageName, FileName = fileName, ID = insertedID });
}
return Json(images);
}
private int CreateImage(string imageName, string fileName)
{
string CreateImageQuery =
@"insert into
INCIDENT_IMAGE_TEST (IMAGE_NAME, [FILE_NAME])
output
inserted.INCIDENT_IMAGE_ID
values
(@imageName, @fileName)";
int returnThis = db.Database.SqlQuery<int>(
CreateImageQuery,
new SqlParameter("imageName", imageName),
new SqlParameter("fileName", fileName)
).Single();
return returnThis;
}
}
And two code-first models:
[Table("INCIDENT_TEST")]
public class Incident
{
[Column("INCIDENT_ID")]
public int IncidentID { get; set; }
[InverseProperty("IncidentID")]
public List<IncidentImage> Images { get; set; }
// ...
}
[Table("INCIDENT_IMAGE_TEST")]
public class IncidentImage
{
[Column("INCIDENT_IMAGE_ID")]
public int IncidentImageID { get; set; }
[Column("INCIDENT_ID")]
public int? IncidentID { get; set; }
// ...
}
The code works, but I feel like I'm fighting the framework instead of using it. Specifically, I'm concerned with two things:
The UploadImages
action is called via AJAX in the view, creates an entry for the image in my DB, and sends back the information that the view needs to update its list via JSON. Works fine, but I don't like the call to SqlQuery()
that executes manually-written SQL to insert the row.
Is there any way to do that by working with an IncidentImage
model after the file is saved?
The HTTP POST version of the Edit
action binds most of the incoming parameters directly to the Incident
model, but the set of check boxes indicating which images should be related to the incident get bound to an int[]
and then handled manually by UpdateRelatedImages()
. After binding, incident.Images
remains null, so I create a new list for it and add all related images.
Is this the "proper" way to do this, or is there some way for me to populate incident.Images
during binding, and only deal with changes to the list, rather than remaking the whole thing?
Edit(int id = 0)
when you say.Single();
you meanSingleOrDefault();
\$\endgroup\$UpdateRelatedImages(Incident incident, int[] selectedImageIDs)
\$\endgroup\$