2013-12-07 11 views
12

Ho il seguente modello:caricare immagine incluso nel modello MVC

public class Photo 
{ 
    public int PhotoId { get; set; } 
    public byte[] ImageData { get; set; } 
    public DateTime DateUploaded { get; set; } 
    public string Description { get; set; } 
    public bool IsActive { get; set; } 

} 

vorrei l'utente sia in grado di entrare nei dettagli per la foto poi inviare il modello del controller. La mia azione di controllo è la seguente:

[HttpPost] 
    public ActionResult Create(WilhanWebsite.DomainClasses.Photo photo) 
    { 
     if (ModelState.IsValid) 
     { 
      photo.DateUploaded = DateTime.Now; 
      _context.Photos.Add(photo); 
      _context.SaveChanges(); 

      return RedirectToAction("Index"); 
     } 
     //we only get here if there was a problem 
     return View(photo); 
    } 

La mia opinione è la seguente:

@using (Html.BeginForm()) 
{ 
@Html.AntiForgeryToken() 

<div class="form-horizontal"> 
    <h4>Photo</h4> 
    <hr /> 
    @Html.ValidationSummary(true) 

    <div class="form-group"> 
     @Html.LabelFor(model => model.ImageData, new { @class = "control-label col-md-2" }) 
     <div class="col-md-10"> 
      <input type="file" name="uploadImages" class="input-files" /> 
     </div> 
    </div> 

    <div class="form-group"> 
     @Html.LabelFor(model => model.DateUploaded, new { @class = "control-label col-md-2" }) 
     <div class="col-md-10"> 
      @Html.EditorFor(model => model.DateUploaded) 
      @Html.ValidationMessageFor(model => model.DateUploaded) 
     </div> 
    </div> 

    <div class="form-group"> 
     @Html.LabelFor(model => model.Description, new { @class = "control-label col-md-2" }) 
     <div class="col-md-10"> 
      @Html.EditorFor(model => model.Description) 
      @Html.ValidationMessageFor(model => model.Description) 
     </div> 
    </div> 

    <div class="form-group"> 
     @Html.LabelFor(model => model.IsActive, new { @class = "control-label col-md-2" }) 
     <div class="col-md-10"> 
      @Html.EditorFor(model => model.IsActive) 
      @Html.ValidationMessageFor(model => model.IsActive) 
     </div> 
    </div> 

    <div class="form-group"> 
     <div class="col-md-offset-2 col-md-10"> 
      <input type="submit" value="Create" class="btn btn-default" /> 
     </div> 
    </div> 
</div> 
} 

La vista viene visualizzata ok e permette all'utente di selezionare un file dal loro disco locale e inserire gli altri dettagli del modello . Il mio problema è che sebbene il modello sia pubblicato nel controller ok, i flag Description, Date e IsActive sono popolati ok - i dati dell'immagine sono nulli.

Qualcuno potrebbe per favore farmi sapere cosa devo cambiare in modo che l'array di byte per la foto sia incluso nel modello pubblicato sul controller?

risposta

19

Il file di input nella vista ha un nome uploadImages. Non riesco a vedere una proprietà con questo nome nel tuo modello di vista. Sembra che tu abbia una proprietà ImageData che è un array di byte, ma non sembra che ci sia un campo di input corrispondente con questo nome nella tua vista.

Questo spiega perché si ottiene null. Potresti fare questo lavoro rispettando la convenzione. Così, per esempio, se avete intenzione di avere un tale campo di inserimento nella vista:

<input type="file" name="uploadImages" class="input-files" /> 

quindi assicurarsi che si dispone di una proprietà del modello di visualizzazione con lo stesso nome. E naturalmente di tipo HttpPostedFileBase.

public HttpPostedFileBase UploadImages { get; set; } 

anche nella vista assicurarsi che si sta impostando il tipo di contenuto corretto di multipart/form-data:

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

Probabilmente potrebbe desiderare di passare attraverso il following blog post per prendere confidenza con le basi di come il caricamento dei i file funzionano in ASP.NET MVC. Ho anche scritto un similar answer here che potresti consultare.

Quindi una volta che si aggiunge alla proprietà HttpPostedFileBase con il nome UploadImages nel vostro modello di vista si potrebbe adattare la vostra azione di controllo per leggere l'array di byte e memorizzarlo vostra proprietà ImageData:

[HttpPost] 
public ActionResult Create(WilhanWebsite.DomainClasses.Photo photo) 
{ 
    if (ModelState.IsValid) 
    { 
     photo.DateUploaded = DateTime.Now; 
     photo.ImageData = new byte[photo.UploadImages.ContentLength]; 
     photo.UploadImages.Read(photo.ImageData, 0, photo.ImageData.Length); 

     _context.Photos.Add(photo); 
     _context.SaveChanges(); 

     return RedirectToAction("Index"); 
    } 

    //we only get here if there was a problem 
    return View(photo); 
} 

Ora tenere a mente che questo è una soluzione assolutamente terribile. Non farlo mai in un'applicazione del mondo reale. In un'applicazione progettata correttamente si avrà un modello di vista che l'azione del controllore avrà come parametro. Non utilizzerai mai direttamente il modello EF autogenerato come parametro per l'azione del controller. Avrai un modello di vista con la proprietà HttpPostedFileBase che verrà mappata al tuo modello di dominio.

Quindi in un'applicazione progettata correttamente si avrà una classe di modello di visualizzazione PhotoViewModel che verrà eseguita dall'azione del controller.

+0

Un'altra grande risposta, grazie a Darin. Creerò un modello di visualizzazione dedicato. Stavo usando un progetto di repository per questa soluzione, ma ho scoperto che l'ulteriore livello di astrazione ha davvero complicato i miei test di unità moq'd. –

+0

Risposta molto bella, grazie! +1 –

+0

non dovrebbe essere questo: 'photo.UploadImages.InputStream.Read' –

1

Modifica questa linea:

@using (Html.BeginForm()) 

A tal:

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

Poi cambia:

<input type="file" name="uploadImages" class="input-files" /> 

A:

<input type="file" name="ImageData" class="input-files" /> 

Poi cambiare questa linea:

public byte[] ImageData { get; set; } 

A tal:

public HttpPostedFileBase ImageData { get; set; } 

Infine, usare un po 'di codice come questo per leggere l'immagine in un array di byte:

var bs = new byte[ImageData.ContentLength]; 
using (var fs = ImageData.InputStream) 
{ 
    var offset = 0; 
    do 
    { 
     offset += fs.Read(bs, offset, bs.Length - offset); 
    } while (offset < bs.Length); 
} 
1

Vista:

@using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" })) 
{ 
    ... 
    <input type="file" id="ImageFile" name="ImageFile" .../> 
    ... 
} 

Controller:

[HttpPost] 
public ActionResult Create(Photo photo, HttpPostedFileBase ImageFile) 
{ 
    byte[] buf = new byte[ImageFile.ContentLength]; 
    ImageFile.InputStream.Read(buf, 0, buf.Length); 
    photo.ImageData = buf; 
    ... 
} 
Problemi correlati