views:

731

answers:

3

I'm trying to build a dynamically generated view. My controller class for Create action looks like this

 public ActionResult Create()
 {
        List<FormMetadata> formItems = GetFormItems();

        return View(formItems);
 }

and the View so far is something like this

<% using (Html.BeginForm())
   {%>
<table>
    <% foreach (var item in Model)
       {
           if (!item.IsActive)
           {
               continue;
           }
    %>
    <tr>
        <td>
            <%=Html.Encode(item.DisplayValue)%>
        </td>
        <td>
            <% 
                if (item.FieldType == "TextBox")
                {%>
            <%=Html.TextBox(item.Field, null, new { tabindex = item.SortOrder })%>
            <%}
                if (item.FieldType == "CheckBox")
                {%>
            <%=Html.CheckBox(item.Field, false, new { tabindex = item.SortOrder })%>
            <%}

            %>
        </td>
        <td>
        </td>
    </tr>
    <%} %>
</table>

I want to show the same view with the values retained when there are validation errors. Code like the following is used to catch the validation errors

 if (string.IsNullOrEmpty(collection[item.ToString()]))
 {
         ModelState.AddModelError(key, "Required.");
 }

How can I show a view with validation errors while retaining the values which have been entered for this scenario?

A: 

EDIT2

I have created a quick sample project which works. There is one thing I do not like and that is that I cannot pass the list itself around. I have to create the blank list every time and read all the values from the textboxes and save this in the list and give this updated list to the new view. Next round same thing. But it works.

Basically:

    public ActionResult About() {
        List<FormMetaData> formItems = GetFormItems();

        return View(formItems);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult About(FormCollection form)
    {
        List<FormMetaData> formItems = GetFormItems();
        //TryUpdateModel(formItems);


        // update project members       
        foreach (var key in form.AllKeys) {
            if (key.ToString().StartsWith("TextBox")) {
                string field = (key.ToString().Replace("TextBox", ""));
                if (!string.IsNullOrEmpty(form.Get(key.ToString()))) {
                    formItems.Find(delegate(FormMetaData t) { return t.Field == field; }).Value = form.Get(key.ToString());
                } 
                else { }
                   // this.ProjectRepository.DeleteMemberFromProject(id, userId);
            }
        }

        ModelState.AddModelError("test", "this is a test error");
        if(ModelState.IsValid)
        {
                ///
        }
        else
        {
            return View(formItems);
        }
        return View(formItems);
    }

    private List<FormMetaData> GetFormItems() {
            List<FormMetaData> output = new List<FormMetaData>();

            FormMetaData temp1 = new FormMetaData("TextBox",true,"temp1","displayText1");
            FormMetaData temp2 = new FormMetaData("TextBox", true, "temp2", "displayText2");
            output.Add(temp1);
            output.Add(temp2);

            return output;
        }

and then you have your view:

<% using (Html.BeginForm()) {%>
<table>
    <% foreach (var item in Model) {
           if (!item.isActive) {
               continue;
           }   %>
    <tr>
        <td>
            <%=Html.Encode(item.DisplayValue)%>
        </td>
        <td>
            <% if (item.FieldType == "TextBox") {%>
            <%=Html.TextBox("TextBox"+item.Field, item.Value)%>
            <%} if (item.FieldType == "CheckBox") {%>
            <%=Html.CheckBox("Value")%>
            <%}%>
        </td>
        <td>
        </td>
    </tr>
    <%} %>
    <p>
            <input type="submit" value="submit" />
        </p>
      <% } %>
</table>

I have uploaded a zipfile for you @ http://www.bastijn.nl/zooi/dynamicSample.rar

EDIT

I have tried this example and it goes wrong with the modelbinder. When I use "FormCollection form" as input to the POST create method the values of my textboxes are there under the key supplied. So you have to either your custom model binder or make a model which will work with the default model binder.

To be more specific. It goes wrong because in this case your textboxes are updating properties in objects inside the List, which is the Model passed. Normally your textboxes are updating properties inside the Object which is also your Model and the key used for the textbox (for automatic modelbinding) is the name of the property you update.

So I suppose the model binder does not bind the values in the textbox to your items in the list since it simply does not know how to do this automatically. It is 3.17 am here right now so I'm off to bed, the question is interesting and I might finish the answer tomorrow.

original

<%=Html.TextBox(item.Field, null, new { tabindex = item.SortOrder })%>

It seems you are generating your form every time with the values set to null.

Try to init them something like:

<%=Html.TextBox(item.Field, **item.Value**, new { tabindex = item.SortOrder })%>

And in your controller, when you check for ModelState.isValid do something like:

if(ModelState.isValid){
     //code when it works
}
else{
    return View(formItems)    // these should contain the just added values
}

That should do the trick.

So, in a simple example you get something like:

public ActionResult Create()
{
    List<FormMetadata> formItems = GetFormItems();

    return View(formItems);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(List<FormMetadata> formItems)
{
    if(ModelState.isValid)
    {
        MyUpdate()
        save()
    }
    else
    {
        return view(formItems)
    }
}

And your view:

<% 
   if (item.FieldType == "TextBox")
   {%>
         <%=Html.TextBox(item.Field, **item.Value**, new { tabindex = item.SortOrder })%>
         <%}
            if (item.FieldType == "CheckBox")
            {%>
              <%=Html.CheckBox(item.Field, **item.Value**, new { tabindex = item.SortOrder })%>
            <%}
bastijn
I have tried this approach. The formItems in the HttpVerbs.Post Create action always sends a null value in the controller.
chrisk
That is probably because the default modelbinder will try to bind the value in the textbox to your key (first value). So actually it must be something like:<%=Html.TextBox("item.Value", item.Value, new { tabindex = item.SortOrder })%>Now it tries to bind the value found in the textbox to your Model.item.Value. Of course you can name this the way you like.
bastijn
edited this answer with a pin down on the problem (as what I suspect it to be).
bastijn
I have found that to bind to a List of complex type, the names in the view need to be indexed. So instead of using "foreach loop" in the view, I am now using a "for loop". This binds the values to my list. Using caching to cache the formItems in the Http.Get Create action and later get a collection of Field and value. This seems to work now.
chrisk
Could you post your final answer POST actionmethod in a seperate answer or as edit on yourself? Might be handy for people having the same problem :).
bastijn
A: 

I'd be checking out NerdDinner first and basically using the same approach.

griegs
A: 

I'm using the following approach now, only working with Textboxes at the moment

The View has

<%
for (int i = 0; i < Model.Count; i++)
{
    var name = "formItems[" + i + "].Field";


    var htmlAttributes = new Dictionary<string, object>
                             {
                                 {"tabindex", Model[i].SortOrder},
                                 {"class", Model[i].ClientSideValidation}
                             };


%>
    <div> <%=Html.Encode(Model[i].DisplayValue)%> 
    <%=Html.TextBox(name, Model[i].DefaultValue, htmlAttributes)%> 
    <%= Html.ValidationMessage(Model[i].Field) %>
    </div>

<% } %>

And the controller Action methods GET

public ActionResult Create()
{
    List<FormMetadata> formItems = GetFormItems();

    HttpContext.Cache[FormCacheKey] = formItems;

    return View(formItems);
}

POST (part of the code)

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(List<FormMetadata> formItems)
    {
        var formDefinition = new List<FormMetadata>();

        try
        {
            if (HttpContext.Cache[FormCacheKey] != null)
            {
                formDefinition = HttpContext.Cache[FormCacheKey] as List<FormMetadata>;
            }
            else
            {
                formDefinition = GetFormItems();
                HttpContext.Cache[FormCacheKey] = formItems;
            }

            var formValues = new Dictionary<string, string>();

            for (int i = 0; i < formDefinition.Count; i++)
            {
                var key = formDefinition[i].Field;

                var value = formItems[i].Field ?? string.Empty;
chrisk