0

Have gone through the first 3 pages of Google and still can't get to the bottom of this. I have a controller which I am using to upload images:

[HttpPost]
    [Authorize(Roles = "Admin,Tradesman,Customer")]
    public ActionResult UploadFile(HttpPostedFileBase file)
    {
        // to do: ensure only valid file types are sent
        try
        {
            if (file.ContentLength > 0)
            {
                using (var ctx = new ApplicationDbContext())
                {
                    if (ModelState.IsValid)
                    {
                        // Need to check we have a current UserId and JobId before we go any furthur
                        var profileData = Session["UserProfile"] as UserProfileSessionData;

                        if (profileData.JobIdGuid.ToString().Length != 36)
                        {
                            // to do: something went horribly wrong! Redirect back to main view
                        }

                        if (profileData.UserIdGuid.ToString().Length != 36)
                        {
                            // to do: something went horribly wrong! Redirect back to main view
                        }

                        var photo = new Photos();

                        photo.Guid = Guid.NewGuid();
                        photo.Url = Server.MapPath("~/Images/2017");
                        photo.Extension = Path.GetExtension(file.FileName);
                        photo.JobGuid = profileData.JobIdGuid;
                        photo.UserIdGuid = profileData.UserIdGuid;
                        photo.Timestamp = DateTime.Now;

                        ctx.Photo.Add(photo);
                        ctx.SaveChanges();

                        string _path = Path.Combine(photo.Url, photo.Guid.ToString() + photo.Extension);
                        file.SaveAs(_path);
                    }
                }         
            }
            ViewBag.Message = "File Uploaded Successfully.";
            return View();
        }
        catch
        {
            ViewBag.Message = "File upload failed.";
            return View();
        }
    }

Each image is saved to a given location, the location saved to the db, happy days. Want I want though is for my images to be displayed on the same page after each upload. The model is as you'd expect just Id, Guid, Url, Extension, UserId, Timestamp.

Here is the view that uploads the images:

@{
ViewBag.Title = "UploadFile";
}

<h2>Upload File</h2>

@using (Html.BeginForm("UploadFile", "Job", FormMethod.Post, new { enctype = "multipart/form-data" }))
{

<div>
    @Html.TextBox("file", "", new { type = "file" }) 

    <br />

    <input type="submit" value="Next" />

    @ViewBag.Message
</div>  

// to do display the images uploaded
}

Is it possible to just have some kind of for...each and have each displayed at the bottom? Anyone know how to do this! Btw this is my first C# MVC app so if this is daft question I apologise. Thanks in advance :)

Martin Cooke
  • 526
  • 1
  • 4
  • 18
  • 2
    You should redirect to the GET action where you will read the data and display in your view. Follow the PRG pattern. – Shyju Nov 24 '17 at 22:05

1 Answers1

2

You should be following the P-R-G pattern. After successfully saving the data in your HttpPost action method, you should do a redirect to your GET action method, where you will read the data you need and pass it to the view where you will display it.

I would create a view model to represent each image and use that

public class ProfileImageVm
{
   public string FileName { set;get;}
   public DateTime CreatedTime { set;get;}
}

Now, for your save partin your http post action method, i would advise you to not save the physical location of the file in the table. The Server.MapPath returns the physical path. Storing that is unnecessary. What if you decide to move the location to some other directory in the server tomorrow? You could simply store the unique fileName. Let's assume that you want to store all the files in the Images/2017 in app root ,you can use Server.MapPath to get the physical location so that you can store the file in disk, but do not use that to store your table record.

var fileName = Path.GetFileNameWithoutExtension(file.FileName);       
photo.Url = fileName ;
photo.Extension = Path.GetExtension(file.FileName);

With this code, it is simply storing the file name(without extension) as it is, not a unique name. That means, if you are uploading a second file with same name, it will overwrite the first one in disk. If you want to generate a unique file name, use the GetUniqueName method from this post.

Now in the GET action method, you read the Photos collection and create a list of our view model from that.

public ActionResult UploadFile()
{
   var list= ctx.Photos
                .Select(x=>new ProfileImageVm { FileName=x.Url + x.Extension ,
                                                CreatedTime = x.Timestamp })
                .ToList();
   return View(list);
}

Now in your UploadFile view will be strongly typed to a list of ProfileImageVm, you can loop through the model data and render the images.

@model List<ProfileImageVm>
@using (Html.BeginForm("UploadFile", "Job", FormMethod.Post, 
                                            new { enctype = "multipart/form-data" }))
{

    @Html.TextBox("file", "", new { type = "file" }) 
    <input type="submit" value="Next" />
}
<h3>Images</h3>
@foreach(var item in Model)
{
   <img src="~/Images/2017/@item.FileName" />
   <p>Uploaded at @item.CreatedTime </p>
}

Now, after successfully saving the photo and the record in table, you will return a redirect response to the GET action.

file.SaveAs(_path);
return RedirectToAction("Upload","Job");

You can also keep the base path ~/Images/2017 in a config settings/constant and use that across your app so if you ever decide to change it to ~/Images/profilepics, there is only one place you have to change.

Shyju
  • 214,206
  • 104
  • 411
  • 497
  • Many thanks for your response, I had a public ActionResult UploadFile() { return View(); } that was used for just returning the view, would I be changing this to the method above public ActionResult UploadFile() or am I creating another separate method? – Martin Cooke Nov 24 '17 at 23:06
  • yes. you will replace your existing method with what i wrote above – Shyju Nov 24 '17 at 23:07
  • Cool have changed that ty, currently its falling on its arse at this line @foreach (var item in Model), before my page even loads, the model is null. Are I missing something? It builds all okay.. – Martin Cooke Nov 24 '17 at 23:14
  • Are you sure you are passing `list` to the View method as i explained in the answer in your GET action method which renders that view ? – Shyju Nov 24 '17 at 23:22
  • Seems to working now so thanks again :) :) :), I added this @if (Model != null) just before it loops my images. Going back to your point on not recording the full url is this because of a security concern as now I can see that if I record this then I may end up displaying the full url on the pages source? – Martin Cooke Nov 24 '17 at 23:49