Coder Social home page Coder Social logo

Comments (11)

mcintyre321 avatar mcintyre321 commented on September 1, 2024

Are you talking about A. getting the values into the form so the form displays correctly, or B. posting the values from the form to the server side ?

Code example would help me understand what you are after.

from formfactory.

SignalRT avatar SignalRT commented on September 1, 2024

I'm talking about B.

In the example I see:

@using (var form = Html.FormForAction((AccountController ctlr, AccountModel model) => ctlr.Update(model)))
{
form.AdditionalClasses = "form-horizontal";
form.Render();
}

But in .NET Core the HTML.FormForAction are commented. And this example seems that are only on the FormFactory.AspMvc.Example, in the FormFactory.AspNetCore.Example It seems that there is no View to this example.

from formfactory.

mcintyre321 avatar mcintyre321 commented on September 1, 2024

Hmm, I think FormForAction never got ported to Core, sorry.
You can create the form tag using normal html/TagHelpers, then render just the fields using FF (using FF.PropertiesFor(model).Render())

from formfactory.

SignalRT avatar SignalRT commented on September 1, 2024

That's not my problema, my problem is how (once that I make submit) I get the values in the model. It's any way to do it automatically or should I do it manually?

from formfactory.

mcintyre321 avatar mcintyre321 commented on September 1, 2024

The posted values should model bind to the model type. What does your controller code look like?

from formfactory.

SignalRT avatar SignalRT commented on September 1, 2024

Ok,

It's my fault. I was generating dynamically and compiling the classes of the forms, because I need that the final user are allowed to change the forms, and that works ok in for the render part, but it doesn´t work automatically for the View to Controller class when I don´t know the type in compile time. I tested it with a class in the project and it works well. I'm only need to figure how to make that the view can pass the data to the controller when both View and Controller knows the type at runtime.

from formfactory.

mcintyre321 avatar mcintyre321 commented on September 1, 2024

from formfactory.

reiphil avatar reiphil commented on September 1, 2024

@mcintyre321 - Trying to figure this out on my end as well...

In an example test controller, I fed it just the nestedFormExample2:

public class TestController : Controller
    {
        public ActionResult Index()
        {
            return View("Index", new IndexModel() { ex = new NestedFormsExample2() });
        }

        public class IndexModel
        {
            public NestedFormsExample2 ex { get; set; }
        }

        public ActionResult Save(NestedFormsExample2 form)
        {
            return Json(form);
        }
    }

When I submit back the output is
{"ContactMethod":{"SocialMediaType":{"FacebookName":null}}}

Even if I select "No contact method" or "Phone contact method". Unsure why this model binding is not hitting correctly. Any tips?

I tried this on both the mvc and aspnetcore examples, both before and after pullrequest 62 (nested icollections).

from formfactory.

mcintyre321 avatar mcintyre321 commented on September 1, 2024

Do you have the json that gets posted?

To get postbacks to work, it may be necessary to use polymorphic deserialization,
which requires json to be posted with type information e.g.:

{
  "ContactMethod": {
    "ObjectType ": "PhoneContactMethod",
    "Number": "123",
    "Type": {
      "ObjectType ": "Landline",
      "Provider": "BT"
    }
  }
}

to

public class TestController : Controller
{
	....

	public ActionResult Save(JToken form)
	{
		NestedFormsExample2 deserialized = form.ToObject<NestedFormsExample2>();
		...
	}
}

You can do this by a. using the https://github.com/manuc66/JsonSubTypes libray to mark up the base objects, so Json.Net knows what Type something should be deserialised as, and b. adding a property to add the discriminator as a hidden field in the form

[JsonConverter(typeof(JsonSubtypes), nameof(ContactMethod.ObjectType))]
[JsonSubtypes.KnownSubType(typeof(PhoneContactMethod), nameof(PhoneContactMethod))]
[JsonSubtypes.KnownSubType(typeof(NoContactMethod), nameof(NoContactMethod))]
[JsonSubtypes.KnownSubType(typeof(SocialMedia), nameof(SocialMedia))]
public abstract class ContactMethod
{
    public ContactMethod(){
		ObjectType = this.GetType().Name;
	}
	[Hidden]
	public string ObjectType { get; set;} 
}

BTW I haven't actually tried JsonSubTypes , but I think it should work! Let me know how you get on

from formfactory.

adamsd308 avatar adamsd308 commented on September 1, 2024

Hi, I work along with @reiphil
Here was my solution that I came up with last night. Beware lengthy post

1st - Created c# interface to standardize polymorphic nested forms

public interface IClassFormSelector<T>
{
    T SelectedClass { get; set; }
    IEnumerable<T> SelectedClass_choices();
}

which is then used like this:

public class ProjectBuilderOptions : Core.Models.IClassFormSelector<Builder>
{
    public ProjectBuilderOptions()
    {
        SelectedClass = new SimpleBuilder() { DescriptionSimple = "Simple" };
    }
    public Builder SelectedClass { get; set; }
    public IEnumerable<Builder> SelectedClass_choices()
    {
        yield return SelectedClass is SimpleBuilder ? SelectedClass.Selected() : new SimpleBuilder();
        yield return SelectedClass is ConcurrentBuilder ? SelectedClass.Selected() : new ConcurrentBuilder();
        yield return SelectedClass is GraphicBuilder ? SelectedClass.Selected() : new GraphicBuilder();
    }
}

and then I have a projectmodel which is the root of the form:

public class ProjectModel {
    [Required]
    public string Name { get; set; }
    [Required]
    public string Description { get; set; }
    public List<Builder> Builders { get; set; } = new List<Builder>();
    private bool Builders_show => false;
    public ICollection<ProjectBuilderOptions> _Form_Builders { get; set; }  = new List<ProjectBuilderOptions>();

    public ProjectModel() {
                
    }
}

Notice that I use _Form_Builders and this just means I want to bind the list of selectedclasses back to Builders later on.
From here the form rendering works as expected.

2nd - fixed a bug in FormFactory.js in order to set the modelname correctly on nested collection elements
I added:

var idPrefix = AssembleId($(this).closest(".ff-collection").find("> ul").children().last());
if (idPrefix != "") {
      modelName = idPrefix + "." + modelName;
}
......
function AssembleId(elementRef) {
    var id = "";
    var parent = $(elementRef).closest(".ff-collection-new-item").find("input[type='hidden']");
    if (parent.length > 0) {
        var input = parent.eq(0);
        id += $(input).prop("name").replace(".Index", "[" + $(input).val() + "]");
    }
    return id;
}

3rd updated FF.cs to include full object type info

var typeVm = new PropertyVm(typeof(string), "__type")
{
    DisplayName = "",
    IsHidden = true,
    Value = model.GetType().FullName + ", " +  model.GetType().Assembly.FullName
};

4th added some javascript to my index.cshtml to handle creating a nice json object out of html form inputs that are rendered to the screen.
To do this I utilize jquery-serialize-object.
The below script will first create a nice json format to represent our form and then second go through and recursively process the json form and handle linking _Form_Builder "SelectedClass" values with Builder array.

$("#frm").submit(function (e) {
    e.preventDefault();
    var frmData = $(this).serializeObject();
    traverse(frmData, process);

    $.ajax({
        type: 'POST',
        url: "/home/save",
        data: JSON.stringify(frmData),
        dataType: "text",
        contentType: "text/plain",
        processData: false
    });
            
});
        
function process(key, value, parent) {

    if (key.indexOf("_Form_") > -1) { 
                
        var arr = [];
        for (var i in value) { 
            if (typeof value[i].SelectedClass !== 'undefined') {
                arr.push(value[i].SelectedClass);
            }
        }
        parent[key.replace("_Form_", "")] = arr;
        delete parent[key];
    }
    if (value !== null && typeof (value) == "object") {
        traverse(value, process);
    }
    console.log(parent);
    console.log(key + " : " + value);
}

function traverse(o, func) {
    for (var i in o) {
        func.apply(this, [i, o[i], o]);
        if (o[i] !== null && typeof (o[i]) == "object") {
            traverse(o[i], func);
        }
    }
}
here is the form after serializing it
{  
   "__type":"Project.Web.Controllers.HomeController+ProjectModel, Project.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
   "Name":"test",
   "Description":"test 2",
   "_Form_Builders":{  
      "Index":"fdc3dd1b-f7bb-4f7d-81e0-080cd585dc29",
      "fdc3dd1b-f7bb-4f7d-81e0-080cd585dc29":{  
         "__type":"Project.Web.Controllers.HomeController+ProjectBuilderOptions, Project.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
         "d29430ad-b6cb-43e6-bdb3-adb3462cf505":"on",
         "SelectedClass":{  
            "__type":"Project.Web.Controllers.HomeController+ConcurrentBuilder, Project.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
            "DescriptionConcurrent":"Concurrent!",
            "_Form_Tasks":{  
               "Index":"dbe505eb-0560-425a-9f6d-bf5c2ca6e0e2",
               "aa54face-cfb3-4801-8743-0661a1e41c68":{  
                  "__type":"Project.Web.Controllers.HomeController+BuildActionOptions, Project.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                  "a04800a4-5a68-41c1-b2b0-6a9a140ca4af":"on",
                  "SelectedClass":{  
                     "__type":"Project.Web.Controllers.HomeController+WinBatchAction, Project.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                     "Description":"Windows Batch Action",
                     "BatchCommand":""
                  }
               },
               "dbe505eb-0560-425a-9f6d-bf5c2ca6e0e2":{  
                  "__type":"Project.Web.Controllers.HomeController+BuildActionOptions, Project.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                  "a04800a4-5a68-41c1-b2b0-6a9a140ca4af":"on",
                  "SelectedClass":{  
                     "__type":"Project.Web.Controllers.HomeController+GraphicsBuildAction, Project.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                     "Name":"",
                     "Disabled":"false",
                     "Category":"",
                     "TestSettingsPath":"",
                     "_testUniqueIdentifier":"",
                     "TestContainers":"",
                     "TrxOutputFilePath":"",
                     "Description":"",
                     "ContinueOnError":"false",
                     "CommandText":""
                  }
               }
            }
         }
      }
   }
}
--------------------------------
and here it is after doing the recursive processing on it (its a little easier to read now)
--------------------------------
{
  "__type": "Project.Web.Controllers.HomeController+ProjectModel, Project.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
  "Name": "test",
  "Description": "test 2",
  "Builders": [
    {
      "__type": "Project.Web.Controllers.HomeController+ConcurrentBuilder, Project.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "DescriptionConcurrent": "Concurrent!",
      "Tasks": [
        {
          "__type": "Project.Web.Controllers.HomeController+WinBatchAction, Project.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
          "Description": "Windows Batch Action",
          "BatchCommand": ""
        },
        {
          "__type": "Project.Web.Controllers.HomeController+GraphicsBuildAction, Project.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
          "Name": "",
          "Disabled": "false",
          "Category": "",
          "TestSettingsPath": "",
          "_testUniqueIdentifier": "",
          "TestContainers": "",
          "TrxOutputFilePath": "",
          "Description": "",
          "ContinueOnError": "false",
          "CommandText": ""
        }
      ]
    }
  ]
}

Finally 5th added our save method which will deserialize the json form body that was posted

[HttpPost]
public JsonResult Save() {
    Stream req = Request.Body;
    string body = "";
    using (StreamReader reader = new StreamReader(Request.Body, System.Text.Encoding.UTF8))
    {
        body = reader.ReadToEnd();
    }
    body = body.Replace("__type","$type");
    var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<ProjectModel>(body, new Newtonsoft.Json.JsonSerializerSettings() {
        TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects,
        TypeNameAssemblyFormatHandling = Newtonsoft.Json.TypeNameAssemblyFormatHandling.Full
    });
  
    return Json(body);
}

So far it has no issues deserializing these polymorphic complex types.
Hopefully this can help someone else out :)

from formfactory.

mcintyre321 avatar mcintyre321 commented on September 1, 2024

Nice, glad to see you are really using FormFactory to it's full potential!

I've used form2js to serialize the forms to the ASP.NET bindable format with collection support - if you find jquery-serialize-object isn't working (e.g. for collection ordering), you could try that.

There is a security risk from using real Type names in your __type variable - an attacker can put any Type name in there and get your system to create and fill an instance. I would recommend using the project I linked above, or using the KnownTypesBinder from https://www.newtonsoft.com/json/help/html/SerializeSerializationBinder.htm

from formfactory.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.