Pergunta

I'm working on a database entry system for services here at my company. Each service can have as many operations as needed. Likewise, each operation can have as many parameters as needed. What I'm trying to do is use dynamic forms to update the data for an operation's parameters within my services/edit view.

I've used dynamic forms for adding operations to a service, but rather than posting them from my service view, I was posting them from operations/create. This shouldn't be an issue, but I thought I'd point it out.

Now, the issue I'm having is posting the data from my parameter forms back to the server. ModelState.isValid is coming back false. When I look at my modelstate, all of my service data members and my operation data members are there, but all of my parameter data members are lost. I used FormCollection to check the names of my dynamic forms and they are in fact following MVC's naming convention for model binding (Parameters[i].DataMembers).

Hopefull somebody can provide some insight for me. Here's some code that might be helpful:

Within my services controller:

    public ActionResult Edit(int id = 0)
    {
        if (id == 0)
        {
            return HttpNotFound();
        }

        //Eager loads the operations and parameters for this service
        var service = db.Services.Include(s => s.Operations.Select(o => o.Parameters)).Where(s => s.ServiceID == id).Single();

        if (service == null)
        {
            return HttpNotFound();
        }

        return View(service);
    }

Within my services/edit view I use an editor template to display the forms for the operations. I also place an empty div after this block of forms which is later populated with forms for the parameters through AJAX.ActionLink:

    @Html.EditorFor(m => Model.Operations)

    <div id="parameter_container">
    </div>

Then within my editor template, I setup my AJAX.ActionLink:

    @Ajax.ActionLink("Edit Parameters", "EditParams", "Services", new { opID = Model.OperationID, ViewContext.FormContext.FormId },
            new AjaxOptions
            {
                UpdateTargetId = "parameter_container",
                InsertionMode = InsertionMode.Replace,
                HttpMethod = "Post"
            })

Then back in my services controller i have:

    public PartialViewResult EditParams(int opID = 0)
    {
        if (opID == 0)
        {
            return PartialView();
        }

        var operation = db.Operations.Include(o => o.Parameters).Where(o => o.OperationID == opID).Single();

        if (operation.parameters == null)
        {
            return PartialView();
        }

        return PartialView("ParameterEditorRow", operation);
    }

ParameterEditorRow is a partial view that is strongly typed to my operation model (This needs to be modeled after operation. Passing Model.Parameters into the editor template generates the required naming convention (Parameters[i].DataMembers). This is simply:

    @model InformationStore.Models.Operation
    @{if (this.ViewContext.FormContext == null)
        {
            this.ViewContext.FormContext = new FormContext();
        }  
     }
    <div class="editor_row">
       @Html.EditorFor(m => Model.Parameters)
    </div>

Then finally, I have an editor template for my parameters. I apologize for the lengthy post, but I've been stuck on this for a couple days now. I just feel like there's something fundamentally wrong with my approach. Any suggestions?

Edit in response to StriplingWarrior

In my edit view, all of my input fields are within this form:

 @using (Html.BeginForm("edit", "services", new {Model.ServiceID}, FormMethod.Post))

and are being posted to:

    [HttpPost]
    public ActionResult Edit(Service service)
    {
        if(ModelState.IsValid)
        {
           //update db
        }
        return View(service)
    }

I used the IE network capture to see what was being posted back, but it just says pending when it breaks, then locks up. I must admit, I don't use it very often, so that could be my problem.

Model Classes (Left out class specific members):

    public class Service
    {
       [Key]
       public int ServiceID {get; set;}

       public virtual List<Operation> Operations {get; set;}
    }

    public class Operation
    {
       [Key]
       public int OperationID { get; set; }

       [Required]
       public int ServiceID { get; set; }

       [Required]
       public virtual Service Service { get; set; }

       public virtual ICollection<Parameter> Parameters { get; set; }
    }

    public class Parameter
    {
       [Key]
       public int ParameterID { get; set; }

       [Required]
       public int OperationID { get; set; }

       [Required]
       public virtual Operation Operation { get; set; }
    }
Foi útil?

Solução

You are calling:

   @Html.EditorFor(m => Model.Parameters)

... which causes the posted parameters to follow the convention Parameters[i].DataMembers. But the model to which you are binding in the long run is not an individual Operation: it is the entire Service, which has an Operations property, and each operation has a Parameters property. So you want the posted parameters to look more like Operations[i].Parameters[i].DataMembers.

To do this, you will need to specify the htmlFieldName when you are invoking the editor:

   @Html.EditorFor(m => Model.Parameters, null, "Operations[0].Parameters")

Of course, the above line will only work for the zeroth operation: you will need to dynamically create the string to pass in the index of the Operation that you're creating an editor for. This will probably require additional finagling to get the original AJAX link to pass the index to the controller in the first place, but you should be able to figure that out.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top