I'm working on a MVC/EF Web Application. In one of the forms I edit a model. The model has an image field (public byte[] BioPhoto)
When I upload an image to that field and save data, ModelState.IsValid is false, because BioPhoto property is null in ModelState. Note that the BioPhoto in model is loaded with image data.
I tried to inject the picture to ModelState using below code but still ModelState.IsValid is false
public ActionResult Edit([Bind(Include = "BusinessId,Name,About,Phone,TollFree,FAX,Email,Bio,BioPhoto")] Business business)
{
if (System.IO.File.Exists("image.jpg"))
{
business.BioPhoto = System.IO.File.ReadAllBytes("image.jpg");
ModelState.SetModelValue("BioPhoto",
new ValueProviderResult(business.BioPhoto, "", System.Globalization.CultureInfo.InvariantCulture));
}
if (ModelState.IsValid)
{
db.Entry(business).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(business);
}
What am I doing wrong. Is there any better solution for this issue?
I read couple of answer in SO but could not figure out how to solve my case.
This is my model
public class Business
{
public int BusinessId { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
public Address address { get; set; }
[Required]
[StringLength(20)]
public string Phone { get; set; }
[StringLength(20)]
public string TollFree { get; set; }
[StringLength(20)]
public string FAX { get; set; }
[Required]
[StringLength(50)]
public string Email { get; set; }
[Required]
[StringLength(100)]
public string WebSite { get; set; }
[Required]
public string About { get; set; }
[Required]
public string Bio { get; set; }
[Required]
public byte[] BioPhoto { get; set; }
}
My View
<div class="form-group">
#Html.LabelFor(model => model.BioPhoto, "BIO Photo (Best Size: 350 x 450)", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<form enctype="multipart/form-data">
<div class="form-group" style="width:400px">
<input id="BioPhoto" type="file" multiple class="file" data-overwrite-initial="false" />
</div>
</form>
#Html.ValidationMessageFor(model => model.BioPhoto, "", new { #class = "text-danger" })
</div>
</div>
Like I said the issue is the form is not posting the BioPhoto to the controller-most likely. You can use Chrome to see what is in the body of the http request.
If you want to add the image even if the controller is not receiving it, then try this.
Try removing only the error related to the image property:
ModelState["BioPhoto"].Errors.Clear();
Then add the image:
business.BioPhoto = System.IO.File.ReadAllBytes("image.jpg");
Then update the model:
UpdateModel(business);
Now if you call ModelState.IsValid, it will take the setting of BioPhoto into consideration and re-evaluate the IsValid.
EDIT:
I suggest to call
ModelState.SetModelValue("BioPhoto", new ValueProviderResult(business.BioPhoto, "", System.Globalization.CultureInfo.InvariantCulture));
to actually push the value to ModelState.Values. this is not neccessary though.
Since you are not using a view model, you should use a parameter of type HttpPostedFileBase for the image. Then in your action method, you can convert it's InputStream property value to byte array and save it.
public ActionResult Edit([Bind(Include = "BusinessId,Name,About,Phone,TollFree,
FAX,Email,Bio")] Business business,HttpPostedFileBase BioPhoto)
{
// to do read BioPhoto.InputStream and convert to your byteArray
// to do :Return something
}
You have to remove the [Required] attribute from your byte array property of your model class.
The best solution is to use a view model which has only properties you needed from your view. You do not need to repost the image from the edit screen everytime. Update that property only if user selects a new image in the edit form. If not do not update the existing value of that column when you save.
public class EditBusinessVm
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
public Address address { get; set; }
[Required]
[StringLength(20)]
public string Phone { get; set; }
public HttpPostedFileBase BioPhoto { set;get;}
// Add other properties as needed.
}
Now your view must be strongly typed to this class
#model EditBusinessVm
#using(Html.BeginForm())
{
#Html.TextBoxFor(s=>s.Name)
<input type="file" name="BioPhoto" />
#Html.HiddenFor(f=>f.Id)
<!-- add other fields needed -->
<input type="submit" />
}
and your httppost action method will use this type as parameter
public ACtionResult Edit(EditBusinessVm model)
{
// purposefully omitting null checks/model validations code here
//get existing entity
var b=db.Businesses.FirstOrDefault(f=>f.BusinessId==model.Id);
//update the properties from the posted view model
b.Name = model.Name;
if(model.BioPhoto!=null) // user selected a new photo.
b.BioPhoto = GetByteArray(model.BioPhoto);
// to do : Set other properties as well.
db.Entry(business).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
private byte[] GetByteArray(HttpPostedFileBase file)
{
MemoryStream target = new MemoryStream();
file.InputStream.CopyTo(target);
return target.ToArray();
}
Related
I'm developng a school project using a html.beginform to when i click in a button i go to a diferent Action, but i need to pass the values that i have inside my #model in the view to the action.How can I do that?
#using (Html.BeginForm("ExportToexcel_Click", "L_AccessPoint", FormMethod.Post))
{
<button type="submit" id="cmdAction" runat="server">
Export To Excel
</button>
}
Inside that action i will create a file, and to do that i need to pass the model that i have in the view to the action ExportToexcel_Click
[HttpPost]
public ActionResult ExportToexcel_Click(dadosPassar dp)
{
var ds = new dadosPassar();
ds = dp;
return RedirectToAction("Index",ds);
}
And thats my model,
public class dadosPassar
{
public List<Stored1>? dados2 { get; set; }
public List<L_AccessPoint>? Aps { get; set; } = new List<L_AccessPoint>();
public List<L_Zone>? Zones { get; set; } = new List<L_Zone>();
public List<int>? ap { get; set; }
public DateTime Inicial { get; set; }
public DateTime Final { get; set; }
public string? AcessoVisita { get; set; }
public string? tempo { get; set; }
public string ApZona { get; set; }
}
Edit:
I need to pass my model.dados2 to my view to i can work with that, how can i do that?
Thats my dados2 structure
public class Stored1
{
public short ap_id { get; set; }
public string ap_name { get; set; }
public int numeroAcessos { get; set; }
//public int month { get; set; }
public int year { get; set; }
public int MES { get; set; }
public int DIA { get; set; }
}
"I'm using a html.beginform to when I click in a button I go to a diferente Action?"
Yes this is very obvious it should redirect to your controller as
per your code. Because <button type="submit" runat="server"> Export To Excel </button> is not correct way to do that. You should try
below way.
Correct Way:
<input type="submit" id="cmdAction" value="Export To Excel"/>
Output:
"But I need to pass the values that I have inside my model aspnet c#"
It seems that you are sending nothing inside your BeginForm submit
action as shown below.
#using (Html.BeginForm("ExportToexcel_Click", "L_AccessPoint", FormMethod.Post))
{
<input type="submit" d="cmdAction" value="Export To Excel"/>
}
Note: Above code will submit null value to your ExportToexcel_Click controller as you are not sending anything
inside the form post action.
Demo:
Tough Its not clear what your plan is. But if you would like to load this
page with some predefined data in that case, you should initialize the
model in your controller Index action then load the value in
hidden mode like below:
Controller Action For Intial View:
public IActionResult Index()
{
var ds = new dadosPassar();
ds.AcessoVisita = "Initial AcessoVisita";
ds.tempo = "Initial Tempo";
ds.ApZona = "Initial Ap Zona";
ds.Final = DateTime.Now;
return View(ds);
}
View:
#model DotNet6MVCWebApp.Models.dadosPassar
#using (Html.BeginForm("ExportToexcel_Click", "L_AccessPoint", FormMethod.Post))
{
<input asp-for="AcessoVisita" hidden class="form-control" />
<input asp-for="tempo" hidden class="form-control" />
<input asp-for="ApZona" hidden class="form-control" />
<input type="submit" d="cmdAction" value="Export To Excel"/>
}
Controller When Submit Button Cliked:
[HttpPost]
public ActionResult ExportToexcel_Click(dadosPassar dp)
{
var ds = new dadosPassar();
ds = dp;
return RedirectToAction("Index", ds);
}
Output:
Hope that would resolve your current issue and guide you to pass values to your controller POST method.
I have the following controller create action in my controller. It takes care that the displayname in the select list will be 'StoreName - StoreAddress'. The Store complexType is stored in Store.
// GET: Purchases/Create
public ActionResult Create()
{
ViewBag.Stores = db.Stores.Select(s => new { DisplayName = s.StoreName.ToString() + " - " + s.Address.ToString(), Store = s});
return View();
}
In the Create View the following code takes care that it will be displayed correctly.
<div class="form-group">
#Html.LabelFor(model => model.Store.StoreName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Store.StoreName, new SelectList(ViewBag.Stores, "Store", "DisplayName"), new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Store.StoreName, "", new { #class = "text-danger" })
</div>
</div>
It will go to the post method of the controller (if I am correct).
// POST: Purchases/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Store,Price,Date")] Purchase purchase)
{
if (ModelState.IsValid)
{
Store store = purchase.Store;
db.Purchases.Add(purchase);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(purchase);
}
However the Store store = purchase.Store will now give a complex Store type with the values for anything other than StoreName set to null. StoreName will be a string.
How can I get a complex type returned that is equal to the selected Store object?
Edit 1:
public class Purchase
{
public int Id { get; set; }
public Store Store { get; set; }
public string Type { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Price { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
public DateTime Date { get; set; }
}
public class PurchaseDBContext : DbContext
{
public DbSet<Purchase> Purchases { get; set; }
public DbSet<Store> Stores { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
public class Store
{
public int StoreId { get; set; }
public string StoreName { get; set; }
public string Address { get; set; }
public string City { get; set; }
[DataType(DataType.PhoneNumber)]
[RegularExpression(#"^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[1-9]((\s|\s?\-\s?)?[0-9])((\s|\s?-\s?)?[0-9])((\s|\s?-\s?)?[0-9])\s?[0-9]\s?[0-9]\s?[0-9]\s?[0-9]\s?[0-9]$", ErrorMessage = "This is not a valid phonenumber")]
public string PhoneNumber { get; set; }
}
Do I need to use additional annotations to set the navigation properties?
There's alot going on in your example, but there isn't a need to pass a complex object if you're using Navigation Properties and your model is setup in a certain way. If you setup a navigation property for Store/StoreId, Entity Framework should infer things for you, so it will simplify your view.
public class Purchase
{
public int StoreId { get; set; }
[ForeignKey("StoreId")]
public Store Store { get; set;}
}
I'd pass a model to the view similar to below. IMO, it's cleaner than using ViewBag.
public class CreatePurchaseModel
{
//To populate a list
public List<Store> AvailableStores { get; set; }
//The actual object to be created
public Purchase Purchase { get; set; }
}
Controller Method:
// GET: Purchases/Create
public ActionResult Create()
{
var vm = new CreatePurchaseModel()
{
AvailableStores = db.Stores.ToList(),
Purchase = new Purchase()
};
return View(vm);
}
Populate a dropdownlist with the AvailableStores property to set the Purchase.StoreId
#Html.DropDownListFor(m => m.Purchase.StoreId, Model.AvailableStores.Select(x => new SelectListItem { Text = x.StoreName.ToString(), Value = x.StoreId.ToString() }))
If it's setup properly, all you need in the post method parameters is the purchase
[ValidateAntiForgeryToken, HttpPost]
public ActionResult Create(Purchase purchase)
{
//purchase.StoreId should have a non zero value
db.Purchases.Add(purchase);
db.SaveChanges();
}
I have 10 text type input controls, 5 on first tab & 5 on second tab on same page.
Now I have a class as
public partial class TravelerMaster
{
public virtual ICollection<TravelerDetail> TravelerDetails { get; set; }
}
which has a collection as TravelerDetail.
public partial class TravelerDetail
{
public byte PackageTypeId { get; set; }
public int? NoOfPackage { get; set; }
public decimal? Weight { get; set; }
public decimal? PricePerKg { get; set; }
public decimal? PricePerPackage { get; set; }
}
How do i bind the controls such that when I post the data, the TravelerMaster object has two objects in its collection.
Please help me. Thanks in advance. Feel free to ask any query.
You can submit a collection of a particular model like this. By applying an index (this can be anything unique but I've used an integer value for ease) you can submit a collection of the same model to the controller:
Form
<div id="tab1">
#Html.Hidden("data.Index","1")
#Html.TextBox("data[1].weight", "", new { #class = "your-class-names", #id = "data[1].weight" })
...other fields...
</div>
<div id="tab2">
#Html.Hidden("data.Index","2")
#Html.TextBox("data[2].weight", "", new { #class = "your-class-names", #id = "data[2].weight" })
...other fields...
</div>
Controller
[HttpPost]
public ActionResult YourControllerMethod(IEnumerable<TravelerDetail> data)
{
if (data==null || data.Count() == 0) throw new Exception("No Data Added");
....other validation...
foreach(var item in data) {
...database work...
}
}
I have a data model like so
public class NewsItem
{
public virtual int Id { get; set; }
public virtual string NewsTitle { get; set; }
public virtual string NewsContent { get; set; }
public virtual byte[] NewsImage { get; set; }
public virtual DateTime DateAdded { get; set; }
public virtual bool IsLive { get; set; }
}
I then display this data through a View like so:
#model BusinessObject.Domain.NewsItem
<div class="row-fluid">
<h3>
#Html.ValueFor(model => model.NewsTitle)
</h3>
<div class="span5">
<img src="~/Content/images/stock.jpg" />
</div>
<div class="span7">
<p>
#Html.ValueFor(model => model.DateAdded)
</p>
<p>
#Html.ValueFor(model => model.NewsContent)
</p>
</div>
</div>
I then save the data using the _db.SaveChanges() in my controller like so:
[Authorize]
[HttpPost]
public ActionResult Create(CreateNewsViewModel input)
{
if (ModelState.IsValid)
{
var news = new NewsItem();
news.NewsTitle = input.nTitle;
news.NewsContent = input.nContent;
news.DateAdded = input.nDateAdded;
news.IsLive = input.nIsLive;
Mydb data = new Mydb();
data.NewsItems.Add(news);
data.SaveChanges();
return View("Index", data.NewsItems);
}
else
{
return View(input);
}
}
Currently I don't have an image upload bit. How would I go about this? In my db I have a binary field, and my data type in my object is a byte[]. But I don't know where I need to handle the Image Upload?
Do I need a seperate action that returns the view? Some pointers on this would be grand.
Cheers
You'll want an input field of type file to upload from the View and an instance of the WebImage to handle the uploaded image:
View:
<input type="file" name="image" />
Controller:
WebImage image = WebImage.GetImageFromRequest();
byte[] toPutInDb = WebImage.GetBytes();
// ... put the byte array into the database
To display the images from your database, you will want a controller Action that retrieves the byte array from your database and returns a FileAction. You can get this image (for the image retrieving controller action) by instantiating another WebImage instance on the bytearray you retrieve from database:
WebImage image = new WebImage(byteArrayFromDb);
File(image.GetBytes(), "image/" + image.ImageFormat, image.FileName);
I would go like this:
in your model class:
public class NewsItem
{
public virtual int Id { get; set; }
public virtual string NewsTitle { get; set; }
public virtual string NewsContent { get; set; }
public virtual string NewsImage { get; set; } //string instead of byte because you don't wanna store your whole image file in your database, but just the path of the image, and the image you will store in a folder somewhere on the server
public virtual DateTime DateAdded { get; set; }
public virtual bool IsLive { get; set; }
}
in your controller:
[Authorize]
[HttpPost]
public ActionResult Create(CreateNewsViewModel HttpPostedFileBase file)// add this
{
if (ModelState.IsValid)
{
if (file != null)
{
file.SaveAs(HttpContext.Server.MapPath("~/Images/") + file.FileName);
car.ImagePath = file.FileName;
}
// the rest of the code...
}
else
{
return View(input);
}
}
Then in your views you should have:
for upload:
<input id="NewsImage" title="Upload a image" type="file" name="file" />
for display in the foreach cycle add:
#Html.DisplayFor(modelItem => item.NewsImage)
don't forget to add enctype = "multipart/form-data" in the Html.BeginForm
I hope this would help.
Have a look at jquery file uploader
You can find sample code for it
Hope this helps
I thought it was a pretty simple issue, and I might be overthinking it, but I can't figure out how to pass just a date string to my view. Here is the entity framework code first class:
public class Onus
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
[MaxLength(140)]
public virtual string Description { get; set; }
public virtual string Details { get; set; }
public virtual DateTime? DueDate { get; set; }
public virtual string Status { get; set; }
}
And here is the part of the controller where I want to change it:
public Onus GetOnus(int id)
{
var onus = db.Onuses.Find(id);
if (onus == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return onus;
}
In the view I am using some ajax and javscript templating to show the data. If you need that code to, I will add it.
***EDIT
Sorry for not being more clear, I don't really have a controller manipulating this, it is all done with ajax:
var getOnus = function (id) {
return $.ajax(onusApiUrl + "/" + id)
};
var viewOnus = function () {
var id = getId(this);
onusServer.getOnus(id).done(viewOnusDetails);
};
var viewOnusDetails = function (onus) {
var output = templates.onusDetails(onus);
$("#onusDetailsOutput").html(output);
};
THere is a lot more javascript than that, but I believe that is all that you should need to see. Oh and the markup:
<div id="Details">
<div id="detailsLeft">
<input type="hidden" value="{{Id}}" id="detailsId" />
<header>
<p><span class="onusTitleDetails">{{Title}}</span><span id="close">X</span></p>
</header>
{{#if DueDate}}
<p class="detailsLabel">Due Date</p>
<p class="onusDueDate">{{DueDate}}</p>
{{/if}}
<p class="detailsLabel">Description</p>
<p class="onusDescriptionDetails">{{Description}}</p>
<p class="detailsLabel">Details</p>
<p class="onusDetails">{{Details}}</p>
</div>
<div id="detailAction">
<div class="editOnus hover">Edit</div>
<div class="deleteOnus hover">Delete</div>
</div>
</div>
<div class="onusStatusDetails">{{Status}}</div>
It would be helpful to see the controller that is invoking the view, but I'll assume it's something like this:
public ActionResult MyController(int id)
{
Onus onus = GetOnus(int id);
return View(onus.DueDate.HasValue ?
onus.DueDate.Value.ToShortDateString() :
"(null)");
}
That will pass just the short date string to your model, which in turn should declare
#model string
What about adding another property to your entity, and then binding to that?
public virtual DateTime? DueDate { get; set; }
public string DueDateString
{
get { return DueDate != null ? DueDate.ToString() : string.Empty; }
}