How to upload image using a form in ASP.NET MVC - c#

I know there are many tutorials out there but I am not able to understand them as they are just posting their codes with poor explanation plus HttpPostedFileBase is breaking my project as soon i add this to my model class project stops working with following erorr:
Value cannot be null.
Parameter name: entitySet
. I want to add a foreign key to my Movies model for images and create a new model images and add pictures through it.
Movie Model:
public class Movie
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public Genre Genre { get; set; }
[Display(Name = "Genre")]
public int GenreId { get; set; }
[Required]
public DateTime DateAdded { get; set; }
[Required]
[Display(Name = "Release Date")]
public DateTime DateReleased { get; set; }
[Required]
[Display(Name = "Number in Stock")]
[Range(1, 20)]
public int Stock { get; set; }
public int? ImageId { get; set; }
public Image ImageModel { get; set; }
}
Image Model:
public class Image
{
public int Id { get; set; }
[Required]
public string UrlOfImage { get; set; }
}
Movie Save Controller:
public ActionResult Save(Movie movie)
{
if (ModelState.IsValid)
{
if (movie.Id == 0)
{
movie.DateAdded = DateTime.Now;
_context.Movies.Add(movie);
}
else
{
var movieInDb = _context.Movies.Single(m => m.Id == movie.Id);
movieInDb.Name = movie.Name;
movieInDb.GenreId = movie.GenreId;
movieInDb.Stock = movie.Stock;
movieInDb.DateReleased = movie.DateReleased;
}
_context.SaveChanges();
return RedirectToAction("Index", "Movies");
}
var viewModel = new NewMovieViewModel(movie)
{
Genres = _context.Genres.ToList()
};
ModelState.Clear();
return View("MovieForm", viewModel);
}
NewMovieViewModel
public class NewMovieViewModel
{
public IEnumerable<Genre> Genres { get; set; }
public int? Id { get; set; }
[Required]
[StringLength(255)]
public string Name { get; set; }
[Display(Name = "Genre")]
[Required]
public int? GenreId { get; set; }
[Display(Name = "Release Date")]
[Required]
public DateTime? DateReleased { get; set; }
[Display(Name = "Number in Stock")]
[Range(1, 20)]
[Required]
public int? Stock { get; set; }
public int? ImageId { get; set; }
public Image Image { get; set; }
public string Title
{
get
{
return Id != 0 ? "Edit Movie" : "New Movie";
}
}
public NewMovieViewModel()
{
Id = 0;
}
public NewMovieViewModel(Movie movie)
{
Id = movie.Id;
Name = movie.Name;
DateReleased = movie.DateReleased;
Stock = movie.Stock;
GenreId = movie.GenreId;
ImageId = movie.ImageId;
}
}

You can try this out.
Your view should look like this:
#using(Html.BeginForm("UploadFile","Upload", FormMethod.Post, new {
enctype="multipart/form-data"}))
{
<div>
#Html.TextBox("file", "", new { type= "file"}) <br />
<input type="submit" value="Upload" />
#ViewBag.Message
</div>
}
Your controller should look like this:
[HttpPost]
publicActionResultUploadFile(HttpPostedFileBase file)
{
try
{
if (file.ContentLength > 0)
{
string _FileName = Path.GetFileName(file.FileName);
string _path = Path.Combine(Server.MapPath("~/UploadedFiles"), _FileName);
file.SaveAs(_path);
}
ViewBag.Message = "File Uploaded Successfully!!";
return View();
}
catch
{
ViewBag.Message = "File upload failed!!";
return View();
}
}

The idea would be to save your blob data separately in some blob storage and the call should be an async. It will be completely separate call and maybe for a particular blob container.
Whether your page is Admin or User facing it doesn't matter. to provide good user experience you either would want to upload the image as soon as a user selects an image in a container and get back the URL, which you would be saving in the metadata table.
Or you could allow first saving all data and after that directing user to update/add an image in the record.
You also need to think through how you would be saving data (normal data and blob data) in bulk. and what would be the better approach to do these same operations in case of mobile devices? by the way, nowadays we should always think from mobile client perspective first.
Hope this would be helpful.

You should define parameter HttpPostedFileBase in your method
public Action Save(Movie movie, HttpPostedFileBase image)
And in your view you should have an input with name "image"
<input type="file" name="image" />

Related

Passing a view-model to view, saving data from text-boxes into the view-model, then transferring that data from the view-model to my models

I don't know if I am going about this the correct way but basically I'm creating an MVC application for a club where I have a Members class, and an Events class. I have a view that displays all the events below
#model IEnumerable<Application.Models.Event>
#{
Layout = null;
}
<h1>Events For this Month!</h1>
<table>
#foreach (var events in Model)
{
<tr>
<td><h2>#events.Name</h2></td>
<td>
#Html.ActionLink("Sign-Up", "SignUpEvent", "User", new { id = events.Id }, null)
</td>
</tr>
}
</table>
when they select, signup next to the event they wish to attend, it takes them to this Action. I created a viewmodel called "MemberEventViewModel that has a member, and an event in it.
public ActionResult SignUpEvent(int id)
{
var Vmodel = new MemberEventViewModel()
{
};
return View(Vmodel);
}
the view returned is where the member enters their pin
#model Application.ViewModels.MemberEventViewModel
#{
Layout = null;
}
<h2>Sign-Up</h2>
#using (Html.BeginForm("SignUp", "User"))
{
#Html.LabelFor(c => c.Member.PIN)
#Html.TextBoxFor(c => c.Member.PIN)
#Html.HiddenFor(c => c.Events.Id)
<button type="submit">Save</button>
}
then finally goes to this controller so I can save and see the event that a member has signed up for
[HttpPost]
public ActionResult SignUp(MemberEventViewModel Vmodel)
{
var EventinDB = _context.Events.SingleOrDefault(c => c.Id == Vmodel.Events.Id);
var MemberinDB = _context.Members.SingleOrDefault(c => c.PIN == Vmodel.Member.PIN);
MemberinDB.Events.Add(EventinDB);
EventinDB.Members.Add(MemberinDB);
return View("ViewEvents", "User");
I'm confused on how to get this working. I want to view what events a member is signed up for, and vice versa. A bridge table called MemberEvents was created when I created the many to many relationship between Events and Members.
also here is my View model class
public class MemberEventViewModel
{
public Member Member { get; set; }
public Event Events { get; set; }
}
here is the events class
public class Event
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
[Display(Name = "Date")]
public DateTime? EventDate { get; set; }
[Required]
[Display(Name = "Start Time")]
public TimeSpan EventStartTime { get; set; }
[Required]
[Display(Name = "End Time")]
public TimeSpan EventEndTime { get; set; }
public int EventTotalTime{ get; set; }
public virtual ICollection<Member> Members { get; set; }
}
and members class
public class Member
{
public int Id { get; set; }
[Required]
[MaxLength(4, ErrorMessage = "PIN must be 4 numbers long"), MinLength(4, ErrorMessage = "PIN must be 4 numbers long")]
public string PIN { get; set; }
[Required]
[Display(Name ="First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Date of Birth")]
public DateTime? Birthdate { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
Looks like there's nothing wrong with how you're adding it but I think you missed context.SaveChanges() after you're adding the MemberEvents object.
[HttpPost]
public ActionResult SignUp(MemberEventViewModel Vmodel)
{
var EventinDB = _context.Events.SingleOrDefault(c => c.Id == Vmodel.Events.Id);
var MemberinDB = _context.Members.SingleOrDefault(c => c.PIN == Vmodel.Member.PIN);
// try using one only
MemberinDB.Events.Add(EventinDB);
// EventinDB.Members.Add(MemberinDB);
// you should save here
_context.SaveChanges();
return View("ViewEvents", "User");
}
Also be sure that you're assigning the properties here;
public ActionResult SignUpEvent(int id)
{
var Vmodel = new MemberEventViewModel()
{
Member = // find the member _context.Members.Where(...)
Events = _context.Events.FirstOrDefault(e=>e.Id == id)
};
return View(Vmodel);
}

How to parse byte[] array from model with IFormFile from viewmodel for file uploading controller?

I have Core MVC application, when for specific entity I have following model:
public class Aktualnosci
{
public long ID { get; set; }
public string Tytul { get; set; }
public string Tresc { get; set; }
public DateTime Dzien { get; set; }
public byte[] AktualnosciImage { get; set; }
}
For uploading the image I am using IFormFile property in my viewmodel, that is called by controller, according to the documentation from >>HERE<<:
public class AktualnosciCreateVM
{
public long ID { get; set; }
[Required(ErrorMessage = "Proszę wypełnić pole.")]
[StringLength(40, ErrorMessage = "Max 40 znaków.")]
public string Tytul { get; set; }
[Required(ErrorMessage = "Proszę wypełnić pole.")]
public string Tresc { get; set; }
[Required(ErrorMessage = "Proszę wypełnić pole.")]
public DateTime Dzien { get; set; }
public IFormFile AktualnosciImage { set; get; }
}
And it is used for creating and editing of the entity. Right now I have troubles with parsing public IFormFile AktualnosciImage { set; get; } and public byte[] AktualnosciImage { get; set; } in my controller's GET method, to have returned viewmodel:
[Authorize(Roles = "Moderatorzy")]
// GET: Aktualnosci/Edit/5
public IActionResult Edit(long? id)
{
if (id == null)
{
return NotFound();
}
Aktualnosci aktualnosci = aktualnosciRepository.AktualnosciList
.FirstOrDefault(m => m.ID == id);
if (aktualnosci == null)
{
return NotFound();
}
else
{
aktualnosciCreateVM.ID = aktualnosci.ID;
aktualnosciCreateVM.Tytul = aktualnosci.Tytul;
aktualnosciCreateVM.Tresc = aktualnosci.Tresc;
aktualnosciCreateVM.Dzien = aktualnosci.Dzien;
//this one gives me an error v
aktualnosciCreateVM.AktualnosciImage = aktualnosci.AktualnosciImage.ToArray();
return View(aktualnosciCreateVM);
}
}
The compilation error is:
Cannot implicitly convert type 'byte[]' to
'Microsoft.AspNetCore.Http.IFormFile'
Is there any way to parse this 2 properties?

Add new value while Create on Scaffold MVC

I have a question. I am new on ASP.NET MVC Code First.
I am creating a simple CRUD using Scaffold MVC.
My Model:
public class Manufacture
{
[Key]
public int ManufactureID { get; set; }
[Required]
[Column("Manufacture", TypeName = "varchar")]
[MaxLength(25)]
[Display(Name = "Manufacture")]
public string ManufactureCode { get; set; }
[Column("ManufactureDescription", TypeName = "varchar")]
[MaxLength(50)]
[Display(Name = "Description")]
public string ManufactureDescription { get; set; }
[Column("IsActive", TypeName = "bit")]
[Display(Name = "Active?")]
public bool IsActive { get; set; }
public DateTime CreatedDateTime { get; set; }
}
My Controller:
public ActionResult ManufactureCreate()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ManufactureCreate([Bind(Include = "ManufactureID,ManufactureCode,ManufactureDescription,IsActive,CreatedDateTime")] Manufacture manufacture)
{
if (ModelState.IsValid)
{
if (db.Manufactures.Any(ac => ac.ManufactureCode.Equals(manufacture.ManufactureCode)))
{
return View();
}
else
{
db.Manufactures.Add(manufacture);
db.SaveChanges();
return RedirectToAction("ManufactureCreate");
}
}
return View(manufacture);
}
I want to add a value on "CreatedDateTime" field using current DateTime. When User click the "Save" button on the View. "CreatedDateTime" field will be filled by current DateTime.
How can I do that?
Please advise.
Thank you.
manufacture.CreatedDateTime=DateTime.Now;
add above the line
db.Manufactures.Add(manufacture);
Modify your else block as below:
else
{
manufacture.CreatedDateTime=DateTime.Now;
db.Manufactures.Add(manufacture);
db.SaveChanges();
return RedirectToAction("ManufactureCreate");
}
and yes, you should use ViewModel here as Stephen suggested in the comment.

Model.get returned Null MVC C#

Upon my post ActionResult Edit, I am receiving an error. System.Web.Mvc.WebViewPage<TModel>.Model.get returned null
My controller:
[HttpPost]
public ActionResult Edit(editRequestViewModel _editRequestViewModel, int id)
{
try
{
if (ModelState.IsValid)
{
using (var db = new HelpDeskContext())
{
db.Entry(_editRequestViewModel.userRequest).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Digest",new { id = _editRequestViewModel.userRequest.ID });
}
}
else
return View();
}
catch (Exception ex)
{
return View("Error", new HandleErrorInfo(ex, "Change", "Browse"));
}
}
My View includes this for the models field to bind:
#Html.DropDownListFor(model => model.userRequest.forApplication, Model.Applications, "Select Application", new { #class = "form-control" })
My Model has the field as nullable int?:
public int? forApplication { get; set; }
It seems to update the other fields in the model with this field just fine on POST. When the request is first created and saved to the DB, it saves fine in that field when its null. It seems to me that nullable should be OK as a value when its posting (Edit ActionResult)?
EDIT: This is my GET Method that populates the View Model which is passed in to the POST.
public ActionResult Edit(int id)
{
try
{
if (ModelState.IsValid)
{
using (var db = new HelpDeskContext())
{
var query = (from m in db.Requests
where m.ID == id
select new editRequestViewModel()
{
Applications = (from r in db.Applications
select new SelectListItem(){
Text = r.Name,
Value = r.ID.ToString()
}).ToList(),
closeReasons = (from r in db.CloseReasons
select new SelectListItem()
{
Text = r.Name,
Value = r.ID.ToString()
}).ToList(),
userRequest = m
}).FirstOrDefault();
return View(query);
}
}
else
return View();
}
catch (Exception ex)
{
return View("Error", new HandleErrorInfo(ex, "Change", "Browse"));
}
}
And my View has #model HelpDeskSolution.ViewModels.editRequestViewModel
EDIT 2: ViewModel and Model
namespace HelpDeskSolution.Models
{
public class Request
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity), Key()]
public int ID { get; set; }
[Required]
[StringLength(99, MinimumLength = 3)]
public string Title { get; set; }
[StringLength(1000, MinimumLength = 1)]
[Required]
public string Description { get; set; }
[Required]
[Display(Name = "Submit Date")]
public DateTime SubmitDate { get; set; }
public DateTime? CloseDate { get; set; }
[Required]
[StringLength(30)]
public string Author { get; set; }
[Required]
[StringLength(30)]
public string AuthDept { get; set; }
[StringLength(30)]
[Display(Prompt = "Text at top of Epicor Screen...(Optional)")]
public string Module { get; set; }
public int Urgency { get; set; }
[StringLength(30)]
public string Type { get; set; }
public int Status { get; set; }
[StringLength(30)]
[Display(Name = "Request For")]
public string RequestFor { get; set; }
[Required]
public bool Closed { get; set; }
[StringLength(30)]
[Display(Name = "Assign To")]
public string AssignedTo { get; set; }
[Display(Name = "Application")]
public int? forApplication { get; set; }
public int? closeReason { get; set; }
public string ClosedBy { get; set; }
[Display(Name = "ID")]
public int? duplicateOf { get; set; }
}
}
Model:
namespace HelpDeskSolution.ViewModels
{
public class editRequestViewModel
{
public Request userRequest { get; set; }
public List<SelectListItem> Applications { get; set; }
public List<SelectListItem> closeReasons { get; set; }
}
}
Ended up solving this with the direction of #StephenMuecke. The reason I was getting the exception is because upon the return View() in the else portion of the post action, it was attempting to return the view without Applications list, as Stephen said. However that led me to realize that there was first a problem with the Model State, hence why it was even going to the else in the first place. I had another field that was being passed null when it wasnt a nullable type.
I simply changed the type to int? and added a migration and the Action result is A'Okay now.

ASP.NET MVC 5 Edit Action - How to write to Multiple DB Tables/Models

Is there any way to somehow combine the data from two models and THEN map them both to the same viewModel in the context of an edit action?
I have never had to update several tables at once in an edit action in ASP.NET MVC with Entity Framework 6.1.3. This is the layout:
I have a DB table called "Address" which has fields for StreetNumber, StreetName, City, State, ZipCode. It has a one-to-one relationship with another table called Bars. As in, a bar can only have one address and one address can only have one bar.
Because I am storing this data in two separate tables, I am having a very difficult time trying to successfully implement an Edit action which takes data from one form (BarForm) and should update both the Bar and Address database tables. See my code:
BarController
public ActionResult Edit(int id)
{
var bar = _context.Bars.SingleOrDefault(m => m.Id == id);
var address = _context.Addresses.SingleOrDefault(a => a.BarId == id);
//Make sure that the id actually exists:
if (bar == null)
{
return HttpNotFound();
}
var viewModel = Mapper.Map<Bar, BarFormViewModel>(bar, new BarFormViewModel());
if (address == null)
{
address = new Address();
}
Mapper.Map<Address, BarFormViewModel>(address, viewModel);
viewModel.IsNew = false;
return View("BarForm", viewModel);
}
[ValidateAntiForgeryToken]
public ActionResult Save(BarFormViewModel bar)
{
if (!ModelState.IsValid)
{
var viewModel = Mapper.Map<BarFormViewModel, BarFormViewModel>(bar, new BarFormViewModel());
viewModel.IsNew = false;
return View("BarForm", viewModel);
}
if (bar.Id == 0)
{
var newbar = Mapper.Map<BarFormViewModel, Bar>(bar);
newbar.LastUpdated = DateTime.UtcNow;
_context.Bars.Add(newbar);
var addressToAdd = Mapper.Map<BarFormViewModel, Address>(bar);
_context.Addresses.Add(addressToAdd);
}
else
{
var barInDb = _context.Bars.Single(b => b.Id == bar.Id);
var addressInDb = _context.Addresses.Single(a => a.BarId == bar.Id);
Mapper.Map<BarFormViewModel, Bar>(bar, barInDb);
Mapper.Map<BarFormViewModel, Address>(bar, addressInDb);
}
_context.SaveChanges();
return RedirectToAction("Index", "Bar");
}
Domain Models:
public class Bar
{
public int Id { get; set; }
public string Name { get; set; }
[Required]
public string GooglePlaceId { get; set; }
public string SundayDiscounts { get; set; }
public string MondayDiscounts { get; set; }
public string TuesdayDiscounts { get; set; }
public string WednesdayDiscounts { get; set; }
public string ThursdayDiscounts { get; set; }
public string FridayDiscounts { get; set; }
public string SaturdayDiscounts { get; set; }
[Display(Name = "Last Updated")]
public DateTime LastUpdated { get; set; }
}
public class Address
{
public int Id { get; set; }
public int? Number { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
public string State { get; set; }
[Required]
public int ZipCode { get; set; }
public Bar Bar { get; set; }
public int BarId { get; set; }
}
View Model which includes both Address and Bar properties:
{
public class BarFormViewModel
{
public int? Id { get; set; }
public string Name { get; set; }
[Required]
[Display(Name = "Google Place ID")]
public string GooglePlaceId { get; set; }
[Display(Name = "Sunday Happy Hour Info:")]
public string SundayDiscounts { get; set; }
[Display(Name = "Monday Happy Hour Info:")]
public string MondayDiscounts { get; set; }
[Display(Name = "Tuesday Happy Hour Info:")]
public string TuesdayDiscounts { get; set; }
[Display(Name = "Wednesday Happy Hour Info:")]
public string WednesdayDiscounts { get; set; }
[Display(Name = "Thursday Happy Hour Info:")]
public string ThursdayDiscounts { get; set; }
[Display(Name = "Friday Happy Hour Info:")]
public string FridayDiscounts { get; set; }
[Display(Name = "Saturday Happy Hour Info:")]
public string SaturdayDiscounts { get; set; }
[Display(Name = "Last Updated")]
public DateTime? LastUpdated { get; set; }
//Address Model Info
public Address Address { get; set; }
public int? AddressId { get; set; }
[RegularExpression("([1-9][0-9]*)", ErrorMessage = "Must be a number")]
public int? Number { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
public string State { get; set; }
[Required]
public int? ZipCode { get; set; }
public bool IsNew { get; set; }
}
The problem here is that I am getting an empty AddressId with this setup, which is causing an exception when the Save action gets run. This is because the BarForm view is getting passed a ViewModel which has been mapped from a Bar object and the Bar domain model actually has no Address information in it, since it is not the Address model/table.
Is there any way to somehow combine the data from both the Address and Bar models and THEN map them both to the same viewModel?
I keep getting a Sequence Contains no Elements error for this line in the Save action:
var addressInDb = _context.Addresses.Single(a => a.Id == bar.AddressId);
I also tried:
var addressInDb = _context.Addresses.Single(a => a.BarId == bar.Id);
Neither work. I understand what the error is saying and have also checked the actual HTML for my hidden Addressid field and it is blank... See code in my BarForm View:
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.AddressId)
#Html.AntiForgeryToken()
Remove the new BarFormViewModel() as the second parameter in your mapping calls as it is not necessary.
In your post action, inside your if statement that checks if the ModelState is valid and if bar.Id == 0, bar is already a view model, so no need to mapping.
And when you create your AutoMapper mapping, you must create a custom property mapping because the Address.Id property will not map automatically to the AddressId property as the name is not the same.
AutoMapper.Mapper.CreateMap<Address, BarFormViewModel>()
.ForMember(dest => dest.AddressId, o => o.MapFrom(source => source.Id));
And then do the same for the inverse mapping.

Categories

Resources