0

I am testing an ASP.NET MVC 5 application with Visual Studio 2017 Community edition.

I am trying to save Assort model to database with following code. I am navigating to Assort Create page with URL /Assort/Create/1A. The parameter 1A is needed on create page of Assort as I need to display some additional information from that parameter on create page itself.

But when I submit the data, 1A parameter value is being inserted as ID value of Assort model, and thus my ModelState is invalid and I am unable to save data.

Can anyone help me?

MODEL

public class Assort
{
    [Key]
    public int ID { get; set; }

    [Display(Name = "Assort No")]
    [Required(ErrorMessage = "Assort No can not be empty.")]
    public int ASSORTNO { get; set; }

    [Display(Name = "Date")]
    [Required(ErrorMessage = "Date can not be empty.")]
    public DateTime DATE { get; set; }

    [Display(Name = "RFNO")]
    [Required(ErrorMessage = "RFNO can not be empty.")]
    [StringLength(50)]
    public string RFNO { get; set; }

    [Display(Name = "Manager")]
    [Required(ErrorMessage = "Manager can not be empty.")]
    public int MANAGER { get; set; }

    [Display(Name = "Caret")]
    [Required(ErrorMessage = "Caret can not be empty.")]
    public decimal CARET { get; set; }

    [Display(Name = "MFG Size")]
    [Required(ErrorMessage = "MFG Size can not be empty.")]
    public decimal MFGSIZE { get; set; }

    [Display(Name = "Total PCS")]
    [Required(ErrorMessage = "Total PCS can not be empty.")]
    public decimal TOTALPCS { get; set; }

    [StringLength(50)]
    public string APPROVALSTATUS { get; set; }

    [Display(Name = "Details")]
    public string DETAILS { get; set; }

    [ScaffoldColumn(false)]
    public DateTime CREATE_TIMESTAMP { get; set; }
    [ScaffoldColumn(false)]
    public DateTime LAST_EDIT_TIMESTAMP { get; set; }

    [UIHint("AssortReturn")]
    public virtual List<AssortReturn> AssortReturn { get; set; }

    public Assort()
    {
        AssortReturn = new List<AssortReturnModel.AssortReturn>();
    }

    [ForeignKey("RFNO")]
    public virtual Rough rough { get; set; }
}

ACTION

[HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(Assort assort)
    {
        if (ModelState.IsValid)
        {
            assort.APPROVALSTATUS = "NOT APPROVED";
            assort.CREATE_TIMESTAMP = DateTime.Now;
            assort.LAST_EDIT_TIMESTAMP = DateTime.Now;
            db.Assorts.Add(assort);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        Initialize(assort.RFNO,"CREATE");
        return View(assort);
    }

VIEW

@using (Html.BeginForm()) 
{
@Html.AntiForgeryToken()

<div class="form-horizontal">

    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(model => model.ASSORTNO, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.ASSORTNO, new { htmlAttributes = new {@readonly="readonly",@Value=ViewBag.ASSORTNO, @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.ASSORTNO, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.DATE, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.DATE, new { htmlAttributes = new {@autofocus="autofocus",@Value=ViewBag.CURRENTDATE, @class = "form-control date" } })
            @Html.ValidationMessageFor(model => model.DATE, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.RFNO, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.RFNO, new { htmlAttributes = new { @readonly = "readonly", @Value = ViewBag.RFNO, @class = "form-control" } })
            @Html.TextBox("AVAILABLECARET",(decimal)ViewBag.AVAILABLECARET,new {@class="form-control txtAvailablecaret",@readonly="readonly" })
            @Html.ValidationMessageFor(model => model.RFNO, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.MANAGER, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @*@Html.EditorFor(model => model.MANAGER, new { htmlAttributes = new { @class = "form-control" } })*@
            @Html.DropDownListFor(model => model.MANAGER, new SelectList(ViewBag.MANAGERLIST, "ID", "USERNAME"), "Select Manager", new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.MANAGER, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.CARET, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.CARET, new { htmlAttributes = new { @class = "form-control txtCaret" } })
            @Html.ValidationMessageFor(model => model.CARET, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.MFGSIZE, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.MFGSIZE, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.MFGSIZE, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.TOTALPCS, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.TOTALPCS, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.TOTALPCS, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.DETAILS, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.DETAILS, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.DETAILS, "", 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 btnCreate" />
        </div>
    </div>
</div>

}

Hemal
  • 3,682
  • 1
  • 23
  • 54
  • why not send '1A' as some parameter other than `ID`? like `create?param1=1A` – sachin Dec 21 '17 at 13:37
  • @sachin What if I wanted to keep the clean URL?Any other suggestion? – Hemal Dec 21 '17 at 13:58
  • 1
    So you want A1, but as a value of a different parameter, no ID? – Andrei Dec 21 '17 at 14:04
  • @Andrei, Yes I want that. – Hemal Dec 21 '17 at 14:07
  • Your editing data so you should ALWAYS use a view model, and that view model can contain a property (say) `public int AssortID { get; set; }` so its not automatically bound from the route values. –  Dec 21 '17 at 20:49

3 Answers3

2

This is because of the default route, which is handling your request. It looks like:

{controller}/{action}/{id}

And so A1 gets bound to ID. If you want a different behavior, say A1 is still a part of the URL, but binds to a different param, say "name", you need a new route for that:

    routes.MapRoute(
        name: "CreateAssort",
        url: "Assort/Create/{name}",
        defaults: new { controller = "Assort", action = "Create"}
    );

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );

Now "name" will hold A1 and not ID. Notice how your custom route comes before the default one. This is important - routing picks the first route that matches the request.

Andrei
  • 55,890
  • 9
  • 87
  • 108
  • As you suggested, routing picks the first route that matches the request. But `Assort/Create/1A` should match both routes. Am I wrong? – Hemal Dec 21 '17 at 14:12
  • Why `id` can not hold the value `1A`? – Hemal Dec 21 '17 at 14:12
  • And also, if I needed to do same for other routes also, my routeconfig file will be full of routes which will be not suitable. – Hemal Dec 21 '17 at 14:19
  • @Hemal, Yes, it matches both routes, which is not uncommon, and that is why rule about picking the first that matches exists. id can absolutely hold the value of A1, but your post makes it sound as if that is not what you want. If you want that for other routes, you can always generalize the route, what I posted if fairly specific. You can even generate them if you like – Andrei Dec 21 '17 at 14:31
1

What you can do is add a hidden input field named ID to your view.

When the form will be submitted, the value from this field will take precedence over the one from your route i.e. '1A' and the model would have ID as 0 if you don't set the hidden input's value.

sachin
  • 2,341
  • 12
  • 24
  • I will not be able to hold that value in hiddenfield. `Assort/Create/1A` url is fired from a hyperlink available in a grid. And there are lots of records similar to this. So this url is fired from a hyperlink. – Hemal Dec 21 '17 at 14:27
  • @Hemal I was talking about adding a hidden field to the view on which your form is. You just have to add '``' and nothing else – sachin Dec 21 '17 at 14:31
0

I had same issue. But problem is when you creating an model.

You need to have two methods.

[HttpGet] // http://localhost/Assort/Create/1
public ActionResult Create(int Id)
{
   ModelState.Remove(nameof(Id)); // this will remove binding
   var assort = new Assort()
   {
    Id = 'whatever',
   ....
   };
   
   return View(assort);
}

[HttpPost] // http://localhost/Assort/Create/
public ActionResult Create(Models.Assort assort)
{
   if (ModelState.IsValid)
    {
        assort.APPROVALSTATUS = "NOT APPROVED";
        assort.CREATE_TIMESTAMP = DateTime.Now;
        assort.LAST_EDIT_TIMESTAMP = DateTime.Now;
        db.Assorts.Add(assort);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    Initialize(assort.RFNO,"CREATE");
    return View(assort);
}

C# ASP MVC Route Model ID bug

Mertuarez
  • 901
  • 7
  • 24