1

I have 3 form inputs for 3 files and I will have to save each of them to separate columns in the database(sqlserver). I am not able to figure out how to add each filename to a separate column. Please help. I am trying to achieve the commented out code in my controller but I am not sure what the syntax would be. My code is as follows:

View:

@Html.TextBoxFor(model => model.SerialAttachment, new { type = "file", name = "file1", id="file1" })     
@Html.TextBoxFor(model => model.CountryAttachment, new { type = "file", name = "file2", id = "file2" })
@Html.TextBoxFor(model => model.OtherAttachment, new { type = "file", name = "file3", id = "file3" })

Controller:

`public ActionResult Create([Bind(Include = "Id,Serial,PinNumbers,SerialAttachment,CountryAttachment,OtherAttachment")] ModelName modelInstance`) {

if (Request.Files.Count > 0)
{
    for (int i = 0; i < Request.Files.Count; i++)
    {
        var fileUp = Request.Files[i];
        if (fileUp != null && fileUp.ContentLength > 0)
        {
            var fname = Path.GetFileName(fileUp.FileName);
            var path = Path.Combine(Server.MapPath(fname));
            fileUp.SaveAs(path);
            // modelInstance.SerialAttachment = fname;
            // modelInstance.CountryAttachment = fname;
            // modelInstance.OtherAttachment = fname;
            db.model.Add(modelInstance);
            db.SaveChanges();
        }
    }
    return RedirectToAction("Index");
}

Model:

public partial class ModelName
{
     public int? Serial { get; set; }

    public int? PinNumbers { get; set; }
    [StringLength(250)]
    public string SerialAttachment { get; set; }

    [StringLength(250)]
    public string CountryAttachment { get; set; }

    [StringLength(250)]
    public string OtherAttachment { get; set; }
}
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
user7221204
  • 99
  • 3
  • 12
  • Start by removing `new { name = "file1" }` etc which does absolutely nothing. And the signature of your POST method should have a parameter which is your model so that those 3 `HttpPostedFileBase` properties are bound in your model –  Sep 27 '17 at 03:23
  • But you commented out code suggests those 3 properties are `string` in which case you cannot bind a file input to a `string` (you need to bind to a property which is `HttpPostedFileBase`) –  Sep 27 '17 at 03:24
  • I only want to save each filename to a different column name. What I have done works for 1 file but I am not sure how to differentiate between the different files – user7221204 Sep 27 '17 at 03:27
  • Make sure `SerialAttachment`, `CountryAttachment` & `OtherAttachment` properties have `HttpPostedFileBase` as their type instead of declared as string (use different string properties to hold file names). Also you can use the viewmodel name to pass as action argument so `Request.Files` can be replaced. – Tetsuya Yamamoto Sep 27 '17 at 03:28
  • Read my comments! You can bind your file input to model properties so that you know which one to save where - then it becomes `if (modelInstance.SerialAttachment != null) { //save to the appropriate database field }` and ditto for the other 2 –  Sep 27 '17 at 03:29
  • @StephenMuecke : Sorry if this is redundant. My modelname.cs file currently has the 3 attachment types as string as you said. If i change them to HttpPostedFileBase, what datatype should I change the columns to in the sqlserver table. – user7221204 Sep 27 '17 at 03:36
  • @user7221204 If you want to store file contents in DB (e.g. images), then you can use `varbinary` type. But if you want just store path to files instead, use `(n)varchar`. – Tetsuya Yamamoto Sep 27 '17 at 03:38
  • Your editing data - do NOT use data models in your view. You create a view model and it will contain 3 properties `public HttpPostedFileBase SerialAttachment { get; set; }` etc. and use just `@Html.TextBoxFor(model => model.SerialAttachment, new { type = "file" })` to bind to. Then you post back the view model and in the POST method initialize an instance of your data model and sets its properties –  Sep 27 '17 at 03:39
  • @StephenMuecke I am unable to understand the post back to view model part. Would you please be able to show me an example code of what you are talking about. i have made some edits to my code to show exactly what I have. – user7221204 Sep 27 '17 at 03:49
  • Can you include the signature of the method in your controller please? Its hard to help without that. – CodingYoshi Sep 27 '17 at 03:52
  • @StephenMuecke Thank you ! – user7221204 Sep 27 '17 at 03:52
  • @CodingYoshi I have added it now. Please check – user7221204 Sep 27 '17 at 03:52
  • In the loop create new instances of `ModelName` and set its properties (some properties can be set from `modelInstance`) and then add the newly created instance to `db.model`. Call `SaveChanges` outside the loop and it will save all the new instances to the db. You are almost there. – CodingYoshi Sep 27 '17 at 04:01

1 Answers1

1

You cannot bind a file input to a string property. First create a view model containing the properties you want in the view (always use a view model when editing data))

public class ModelNameVM
{
    public int? PinNumbers { get; set; }
    public HttpPostedFileBase SerialAttachment { get; set; }
    public HttpPostedFileBase CountryAttachment { get; set; }
    public HttpPostedFileBase OtherAttachment { get; set; }
}

and add display and validation attributes as appropriate

Your view will then be

@model ModelNameVM
....
@using (Html.BeginForm())
{
    ....
    @Html.TextBoxFor(m => m.SerialAttachment, new { type = "file" })
    @Html.TextBoxFor(m => m.CountryAttachment , new { type = "file" })
    ....

Note that using new { name = "..." } does nothing and there is no need to overwrite the default id attribute created by the HtmlHelper methods.

The code in your POST method will then be

public ActionResult Create(ModelNameVM model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    // Initialize a new instance of your data model and map values from the view model
    ModelName data = new ModelName
    {
        PinNumbers = model.PinNumbers
    };
    if (model.SerialAttachment != null && model.SerialAttachment.ContentLength > 0)
    {
        string fileName = Path.GetFileName(model.SerialAttachment.FileName);
        ..... 
        model.SerialAttachment.SaveAs(path);
        data.SerialAttachment = path
    }
    .... // repeat for CountryAttachment and OtherAttachment 
    db.model.Add(data);
    db.SaveChanges();
    return RedirectToAction("Index");
}

Note you do not need a [Bind] attribute when using a view model (your already protected against over-posting attacks.

In addition, you are not mapping to a folder in your app to upload the files to. You would need something like

var path = Path.Combine(Server.MapPath("~/Images"), fileName);
  • Thank you, i think i understand. I am trying it out now and will get back to you soon. – user7221204 Sep 27 '17 at 04:24
  • Worked perfectly ! Thank you for explaining so clearly ! – user7221204 Sep 27 '17 at 04:43
  • If I were to append an auto id field namely "Id" to the file name and assign it to path how would I be able to get the id value before it has been inserted. Like the following: var path = Path.Combine(Networkpath, data.Id+fileName); I am getting the id as 0 since i am trying to use an id that hasn't been inserted into the db yet. – user7221204 Sep 27 '17 at 15:20
  • Why would you want to do that? (and the only way would be to to save the object first, get the ID and the modify the path and save again). What you should be doing is saving the file name as a `Guid` to ensure its unique and cannot be overwritten, and also displaying the actual file name in a separate (say) `SerialAttachmentDisplayName` field so that it can be displayed back to the user. –  Sep 27 '17 at 23:25
  • Thanks for the help. I need the id in the naming convention inorder to identify which id is associated with which attachment in the file system. I will not be storing that name in the db. – user7221204 Sep 29 '17 at 01:40
  • It unclear why you want to do that (it awful practice). But if you do, then you need to save the object first. but if you so not also then update the name in the database, then the file name in the database will not then match the file name in the file system (making it useless), so you do need to save it again –  Sep 29 '17 at 01:45
  • ModelName data = new ModelName { PinNumbers = null }; db.model.Add(data); db.SaveChanges(); data.PinNumbers = model.PinNumbers; db.model.Add(data); db.SaveChanges(); I tried this but it adds one empty row and the data to the next row. – user7221204 Sep 29 '17 at 03:02
  • You cannot `add` it twice - you just need to mark it as modified the 2nd time and save it (but I'm sorry, but I cant help you any further since you insist on doing bad practice) –  Sep 29 '17 at 03:46
  • I really appreciate your help. I would just like to clarify the reason. I am not exactly using the id itself. It is a different auto increment column name(appended with another prefix) that will be common to db records and file systems(unable to disclose details). I understand the Guid method you mentioned is a best practice but the decision to go this way was not mine to make. – user7221204 Sep 29 '17 at 04:20