Reputation: 152
I am looking for support in uploading an image (as byte!) into a DB along with other properties. I have to admit, being a newbie to programming I was struggling quite a bit to get this done, however, I kind of got it to work. Now my issue is that whenever I try to leverage the model, it would stop working with an error message popping up saying the following:
The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.
ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.ComponentModel.DataAnnotations;
namespace errandomWeb.Models
{
public class PhotoCompetition
{
public int ID { get; set; }
public string UserID { get; set; }
public string FirstName { get; set; }
public string Email { get; set; }
public byte[] CompetitionPicture { get; set; }
//[Required]
[Display(Name = "by checking this box I accept the Terms & Conditions")]
//[CheckBoxRequired(ErrorMessage = "Please accept our Terms & Conditions to participate.")]
public bool TermsAndConditionsAccepted { get; set; }
public DateTime TimeStamp { get; set; }
}
}
View:
@model errandomWeb.Models.PhotoCompetition
@{
ViewBag.Title = "Become Our Model";
}
<div id="photoCompetitionContainer" class="manageContainer">
<div id="photoCompetitionHeaderSection" class="manageHeaderSection">
<h1 id="photoCompetitionHeaderTitle" class="manageHeaderTitle">
@ViewBag.Title
</h1>
<img id="photoCompetitionHeaderProfilePicture" class="manageHeaderProfilePicture" src="@Url.Action("UserPicture", "Manage")" />
<p id="photoCompetitionHeaderPersonalizationGeneric" class="manageHeaderPersonalization">
Hello
</p>
<p id="photoCompetitionHeaderPersonalizationName" class="manageHeaderPersonalization">
@Html.TextBoxFor(m => m.FirstName, new { @id = "photoCompetitionHeaderUserName", @class = "manageHeaderUserName", @placeholder = "Stranger", @disabled = true })
</p>
</div>
@Html.Partial("_ProfileLogout")
<div id="photoCompetitionContextSection" class="manageContextSection">
<p id="photoCompetitionContext" class="manageContext">
Want to become our model?
</p>
</div>
<div id="photoCompetitionValidationSection" class="manageValidation">
@Html.ValidationSummary("", new { @id = "photoCompetitionValidation", @class = "manageValidation" })
</div>
<section id="photoCompetition" class="manageForm">
@using (Html.BeginForm("UploadCompetitionPicture", "errandom", FormMethod.Post, new { @id = "photoCompetitionForm", @class = "form-horizontal", @role = "form", @enctype = "multipart/form-data" }))
{
@Html.AntiForgeryToken()
<div id="photoCompetitionSection" class="manageSection">
<p id="photoCompetitionSectionTitle" class="manageSectionTitle">
Upload your picture and be selected as our model!
</p>
@Html.HiddenFor(m => m.UserID)
@Html.HiddenFor(m => m.Email)
@Html.HiddenFor(m => m.FirstName)
@Html.HiddenFor(m => m.TimeStamp)
<div id="photoCompetitionProfilePictureArea" class="manageArea row">
@Html.LabelFor(m => m.CompetitionPicture, new { @id = "photoCompetitionProfilePictureLabel", @class = "manageLabel col-xs-offset-1 col-xs-10 col-sm-offset-1 col-sm-10 col-md-offset-1 col-md-3 col-lg-offset-1 col-lg-4" })
<a id="photoCompetitionProfilePictureSelectionButton" class="manageField col-xs-offset-1 col-xs-10 col-sm-offset-1 col-sm-10 col-md-offset0 col-md-7 col-lg-offset-0 col-lg-6" href="#">
select a file...
</a>
@Html.TextBoxFor(m => m.CompetitionPicture, new { @id = "photoCompetitionProfilePictureField", @class = "manageField col-xs-offset-1 col-xs-10 col-sm-offset-1 col-sm-10 col-md-offset-0 col-md-7 col-lg-offset-0 col-lg-6", @type = "file", @style = "display: none" })
</div>
<div id="photoCompetitionTermsAndConditionsArea" class="manageArea row">
@Html.CheckBoxFor(m => m.TermsAndConditionsAccepted, new { @id = "photoCompetitionTermsAndConditionsField", @class = "photoCompetitionTermsAndConditionsField" })
@Html.LabelFor(m => m.TermsAndConditionsAccepted, new { @id = "photoCompetitionTermsAndConditionsLabel", @class = "photoCompetitionTermsAndConditionsLabel" })
@Html.ValidationMessageFor(m => m.TermsAndConditionsAccepted, "", new { @id = "photoCompetitionTermsAndConditionsValidation", @class = "manageValidation col-xs-offset-1 col-xs-10 col-sm-offset-1 col-sm-10 col-md-offset-4 col-md-7 col-lg-offset-5 col-lg-6" })
</div>
<script>
jQuery("#photoCompetitionProfilePictureSelectionButton").click(function () {
$("#photoCompetitionProfilePictureField").click();
});
</script>
<script>
$("#photoCompetitionProfilePictureField").change(function () {
var fullFileName = $("#photoCompetitionProfilePictureField").val()
$("#photoCompetitionProfilePictureSelectionButton").html(fullFileName.substr(fullFileName.lastIndexOf('\\') + 1));
});
</script>
<div id="photoCompetitionCroppingArea" class="manageArea row">
<img id="photoCompetitionOriginal" class="photoCompetitionImage" src="" alt="" style="display: none" />
<canvas id="photoCompetitionCropped" class="photoCompetitionImage" height="5" width="5"></canvas>
</div>
<div id="photoCompetitionButtonArea" class="manageArea row">
<input id="photoCompetitionButtonCrop" class="manageButton col-xs-offset-1 col-xs-10 col-sm-offset-1 col-sm-10 col-md-offset-1 col-md-10 col-lg-offset-1 col-lg-10" type="button" value="Crop" style="display: none" />
<input id="photoCompetitionButtonUpload" class="manageButton col-xs-offset-1 col-xs-10 col-sm-offset-1 col-sm-10 col-md-offset-1 col-md-10 col-lg-offset-1 col-lg-10" type="submit" value="Save" style="display: none" />
<input id="photoCompetitionCropX" class="photoCompetitionData" name="photoCompetitionCropX" type="hidden" />
<input id="photoCompetitionCropY" class="photoCompetitionData" name="photoCompetitionCropY" type="hidden" />
<input id="photoCompetitionCropW" class="photoCompetitionData" name="photoCompetitionCropW" type="hidden" />
<input id="photoCompetitionCropH" class="photoCompetitionData" name="photoCompetitionCropH" type="hidden" />
<input id="photoCompetitionCroppedPicture" class="photoCompetitionData" name="photoCompetitionCroppedPicture" type="hidden" />
</div>
</div>
}
</section>
<div id="photoCompetitionReturnToMenuSection" class="manageReturnToMenuSection">
@Html.ActionLink("Return to Menu", "Index", "", htmlAttributes: new { @id = "photoCompetitionReturnToMenuButton", @class = "manageReturnToMenuButton" })
</div>
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/tapmodo/Jcrop/master/js/jquery.Jcrop.min.js"></script>
<script type="text/javascript">
$(function () {
if ($('#photoCompetitionCroppingArea').width() > 700) {
$('#photoCompetitionProfilePictureField').change(function () {
$('#photoCompetitionOriginal').hide();
var reader = new FileReader();
reader.onload = function (e) {
$('#photoCompetitionOriginal').show();
$('#photoCompetitionOriginal').attr("src", e.target.result);
$('#photoCompetitionOriginal').Jcrop({
onChange: SetCoordinates,
onSelect: SetCoordinates,
aspectRatio: 1,
boxWidth: 600,
addClass: 'photoCompetitionCropping'
});
}
reader.readAsDataURL($(this)[0].files[0]);
});
}
else {
$('#photoCompetitionProfilePictureField').change(function () {
$('#photoCompetitionOriginal').hide();
var reader = new FileReader();
reader.onload = function (e) {
$('#photoCompetitionOriginal').show();
$('#photoCompetitionOriginal').attr("src", e.target.result);
$('#photoCompetitionOriginal').Jcrop({
onChange: SetCoordinates,
onSelect: SetCoordinates,
aspectRatio: 1,
boxWidth: 250,
addClass: 'photoCompetitionCropping'
});
}
reader.readAsDataURL($(this)[0].files[0]);
});
}
$('#photoCompetitionButtonCrop').click(function () {
var x1 = $('#photoCompetitionCropX').val();
var y1 = $('#photoCompetitionCropY').val();
var height = $('#photoCompetitionCropH').val();
var width = $('#photoCompetitionCropW').val();
var canvas = $("#photoCompetitionCropped")[0];
var context = canvas.getContext('2d');
var img = new Image();
img.onload = function () {
canvas.height = height;
canvas.width = width;
context.drawImage(img, x1, y1, width, height, 0, 0, width, height);
$('#photoCompetitionCroppedPicture').val(canvas.toDataURL());
$('#photoCompetitionButtonUpload').show();
$('#photoCompetitionCropped').hide();
$('#photoCompetitionButtonCrop').hide();
};
img.src = $('#photoCompetitionOriginal').attr("src");
});
});
function SetCoordinates(c) {
$('#photoCompetitionCropX').val(c.x);
$('#photoCompetitionCropY').val(c.y);
$('#photoCompetitionCropW').val(c.w);
$('#photoCompetitionCropH').val(c.h);
$('#photoCompetitionButtonCrop').show();
};
</script>
}
Controller:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using errandomWeb.Models;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
namespace errandomWeb.Controllers
{
[Authorize]
public class errandomController : Controller
{
private ApplicationDbContext DB = new ApplicationDbContext();
// GET: /errandom/PhotoCompetition
public ActionResult PhotoCompetition()
{
var model = new PhotoCompetition
{
UserID = User.Identity.GetUserId(),
Email = User.Identity.GetUserName(),
FirstName = User.Identity.Name,
TimeStamp = DateTime.UtcNow.ToUniversalTime()
};
return View(model);
}
// POST: /errandom/PhotoCompetition
// 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 UploadCompetitionPicture()
{
string croppedImage = Request.Form["photoCompetitionCroppedPicture"];
byte[] imageBytes = Convert.FromBase64String(croppedImage.Split(',')[1]);
var userId = User.Identity.GetUserId();
var participation = new PhotoCompetition
{
CompetitionPicture = imageBytes,
UserID = User.Identity.GetUserId(),
FirstName = "testcase",
Email = User.Identity.GetUserName(),
TermsAndConditionsAccepted = false,
TimeStamp = DateTime.UtcNow.ToUniversalTime(),
};
DB.PhotoCompetition.Add(participation);
DB.SaveChanges();
return View("Edit");
}
}
}
I would like to include
TermsAndConditionsAccepted = model.TermsAndConditionsAccepted
so the value is actually being taken from the form a user would fill out, but for aforementioned reasons I am encountering issues.
Appreciate any help I am getting here, thx!
Upvotes: 0
Views: 162
Reputation: 152
I finally got it done. Here's the updated controller code:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult UploadCompetitionPicture([Bind(Exclude = "CompetitionPicture")]PhotoCompetition model)
{
string croppedImage = Request.Form["photoCompetitionCroppedPicture"];
byte[] imageBytes = Convert.FromBase64String(croppedImage);
var userId = User.Identity.GetUserId();
var participation = new PhotoCompetition
{
UserID = User.Identity.GetUserId(),
FirstName = "fuckingtest",
Email = User.Identity.GetUserName(),
TermsAndConditionsAccepted = true,
TimeStamp = DateTime.UtcNow.ToUniversalTime(),
};
participation.CompetitionPicture = imageBytes;
DB.PhotoCompetition.Add(participation);
DB.SaveChanges();
return View("Edit");
}
I guess, what made the difference is excluding the picture in the first place, then submitting all other fields and after that adding the picture separately. Not sure why, but glad I figured it out. Thx anyone who contributed!
Upvotes: 0
Reputation: 2226
The error has nothing to do with EF, it is in this line, in the call to Convert
method in the controller:
byte[] imageBytes = Convert.FromBase64String(croppedImage.Split(',')[1]);
There is something wrong in the results of the Split
. Try to inspect that value and see how it looks like.
What can be happening? The value you are trying to decode as base64 is generated in the client javascript in this line, by the function toDataURL()
:
$('#photoCompetitionCroppedPicture').val(canvas.toDataURL());
The returned value includes a prefix depending on the image format. The default format used by this function is png, so it returns a value like this:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNby...
The real value is after the prefix, separated by a comma, that is why you use the split
when reading it in your controller.
But perhaps the format prefix you receive is different and your split is not getting the right value. Try to debug and see the real value, by doing something like this:
console.log(canvas.toDataURL());
Perhaps the value you get in the server is not the same that you generated in the client (it may be truncated, for instance). Compare a console trace of the value in the client (generated with toDataURL
) and the value in the server (the contents of your variable croppedImage
, or the values after doing the Split
).
As a final note, try to use a regular expression instead of a Split
by comma separator to remove the unwanted prefix from your base64 value. It will be safer. Here you have some examples of expressions to do this.
More info on toDataURL
function here.
Upvotes: 1