Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot make Byte a Required Field

Tags:

c#

asp.net-mvc

Have an app where I would like to upload an image. I am using byte array for this.

The image is required but when I put that annotation on the variable and try create an image, it gives me the error message when it should have the image there. It also returns that the Model is Invalid.

When I remove the Required, it works but it can also be set as null which is what I don't want.

There doesn't seem to be a lot on the topic on Stack Overflow.

Here is my Model

[Key]
public int InvoiceId { get; set; }

[Required(ErrorMessage = "Company is required")]
public string Company { get; set; }

[Required(ErrorMessage = "Description is required")]
public string Description { get; set; }

[Required(ErrorMessage = "Amount is required")]
public decimal Amount { get; set; }

[Required(ErrorMessage = "Picture of Invoice Required")]
public byte[] PictureOfInvoice { get; set; }

And my controller:

[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Parish Admin, Priest, Administrator")]
public ActionResult Create([Bind(Include = "InvoiceId,Company,Description,Amount,PictureOfInvoice,DateReceived,ChurchId")] Invoice invoice,HttpPostedFileBase File)
{
    if (ModelState.IsValid)
    {
        if (File != null && File.ContentLength > 0)
        {                   
            invoice.PictureOfInvoice = new byte[File.ContentLength];
            File.InputStream.Read(invoice.PictureOfInvoice, 0, File.ContentLength);

        }
        else
        {
            TempData["Error"] = "Upload an Image";
        }

        db.Invoices.Add(invoice);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    ViewBag.ChurchId = new SelectList(db.Churches, "ChurchId", "Name", invoice.ChurchId);
    return View(invoice);
}

My View just in case its something there:

<h2>Add New Invoice</h2>

@if (TempData["Error"] != null)
{
    <div style="color:red">@TempData["Error"]</div>
}

@using (Html.BeginForm("Create", "Invoices", FormMethod.Post, new { @class = "form-horizontal", enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary()

    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

    <div class="form-horizontal">
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.Company, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Company, new { htmlAttributes = new { @class = "form-control", style = "width:20em;" } })
                @Html.ValidationMessageFor(model => model.Company, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Amount, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Amount,new { htmlAttributes = new { @class = "form-control", style = "width:20em;" } })
                @Html.ValidationMessageFor(model => model.Amount, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.DateReceived, "DateRecieved", htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.DateReceived, new { htmlAttributes = new { @class = "form-control", style = "width:20em;" } })
                @Html.ValidationMessageFor(model => model.DateReceived, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Description, new { htmlAttributes = new { @class = "form-control", style = "width:20em;" } })
                @Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.PictureOfInvoice, "Picture of Invoice", htmlAttributes: new { @class = "control-label col-md-2"})
            <div class="col-md-10">
                <input type="file" name="File" id="File" style="width: 50%;" />
                @Html.ValidationMessageFor(model => model.PictureOfInvoice, "", new { @class = "text-danger" })
                <output id="list"></output>
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.ChurchId, "Church Name", htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.DropDownList("ChurchId", null, htmlAttributes: new { @class = "form-control", style = "width:20em;" })
                @Html.ValidationMessageFor(model => model.ChurchId, "", new { @class = "text-danger" })
            </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>
}

<div>
    <u>
        @Html.ActionLink("Back to List", "Index")
    </u>
</div>


<script src="~/Scripts/jquery.datetimepicker.js"></script>

<script>
    $('#DateReceived').datetimepicker({
        format: 'd/m/Y',
        weeks: true,
        disableWeekDays: [0, 1, 3, 4, 5, 6],
        timepicker: false,
        inline: false
    });

    function handleFileSelect(evt) {
        var files = evt.target.files; // FileList object

        // Loop through the FileList and render image files as thumbnails.
        for (var i = 0, f; f = files[i]; i++) {

            // Only process image files.
            if (!f.type.match('image.*')) {
                continue;
            }

            var reader = new FileReader();

            // Closure to capture the file information.
            reader.onload = (function (theFile) {
                return function (e) {
                    // Render thumbnail.
                    var span = document.createElement('span');
                    span.innerHTML = ['<img class="thumb" src="', e.target.result,
                        '" title="', escape(theFile.name), '"/>'
                    ].join('');
                    document.getElementById('list').insertBefore(span, null);
                };
            })(f);

            // Read in the image file as a data URL.
            reader.readAsDataURL(f);
        }
    }

    document.getElementById('File').addEventListener('change', handleFileSelect, false);
</script>
like image 827
Chloe Finn Avatar asked Apr 26 '18 11:04

Chloe Finn


1 Answers

The mixing of concerns here is cause problems. It appears you are trying to use an entity as the model for the view and have it satisfy both UI validation and persistence validation.

Separate the two concerns.

Create a view model specific to the desired behavior of the view. The model should also include IEnumerable<SelectListItem> ChurchList property to populate the drop down.

public class CreateInvoiceViewModel {

    [Required(ErrorMessage = "Company is required")]
    public string Company { get; set; }

    [Required(ErrorMessage = "Description is required")]
    public string Description { get; set; }

    [Required(ErrorMessage = "Amount is required")]
    public decimal Amount { get; set; }

    [Required(ErrorMessage = "Picture of Invoice Required")]
    public HttpPostedFileBase File { get; set; }

    public int ChurchId { get; set; }

    public IEnumerable<SelectListItem> ChurchList { get; set; }

    //...other properties
}

and set that as the model of the view

@model CreateInvoiceViewModel

If creating a new invoice there is no id assigned as yet. That means when posting the model as you current have it, the model state cannot be valid as InvoiceId, which is tagged as Required is not provided.

The uploaded file (invoice picture) should also be included in the view model and should use @Html.TextBoxFor(m => m.File, new { type = "file" }) to get client side validation. The model binder will set that property based on whether a matching input was provided.

<div class="form-group">
    @Html.LabelFor(model => model.File, "Picture of Invoice", htmlAttributes: new { @class = "control-label col-md-2"})
    <div class="col-md-10">            
        @Html.TextBoxFor(model => model.File, new { type = "file", style = "width: 50%;"})
        @Html.ValidationMessageFor(model => model.File, "", new { @class = "text-danger" })
        <output id="list"></output>
    </div>
</div>

<div class="form-group">
    @Html.LabelFor(model => model.ChurchId, "Church Name", htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.DropDownListFor(model => model.ChurchId, (IEnumerable<SelectListItem>)@Model.ChurchList, htmlAttributes: new { @class = "form-control", style = "width:20em;" })
        @Html.ValidationMessageFor(model => model.ChurchId, "", new { @class = "text-danger" })
    </div>
</div>

So now on the controller side, [Bind] should be removed as it's not needed when using a view model.

[HttpGet]
[Authorize(Roles = "Parish Admin, Priest, Administrator")]
public ActionResult Create() {
    var model = new CreateInvoiceViewModel {
        //set default property values as needed
    };

    model.ChurchList = new SelectList(db.Churches, "ChurchId", "Name");

    //...

    return View(model);
}

[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Parish Admin, Priest, Administrator")]
public ActionResult Create(CreateInvoiceViewModel model) {
    if (ModelState.IsValid) {
        var file = model.File;
        //copy properties over to entity
        var invoice = new Invoice {
            Company = model.Company,
            Description = model.Description,
            Amount = model.Amount,
            DateReceived = model.DateReceived,
            ChurchId = model.ChurchId,
            //create array for file contents
            PictureOfInvoice = new byte[file.ContentLength]
        };
        //populate byte array
        file.InputStream.Read(invoice.PictureOfInvoice, 0, file.ContentLength);

        db.Invoices.Add(invoice);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    //if we get this far model state is invalid.
    //return view with validation errors.
    model.ChurchList = new SelectList(db.Churches, "ChurchId", "Name", model.ChurchId);
    return View(model);
}

The model state will provide the necessary feedback if the model requirements are not valid. Removing the need for using the temp data and the file check.

like image 56
Nkosi Avatar answered Oct 10 '22 19:10

Nkosi