Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

[Question] Optional validation of items in collection #5678

Closed
joacar opened this issue Jan 12, 2017 · 5 comments
Closed

[Question] Optional validation of items in collection #5678

joacar opened this issue Jan 12, 2017 · 5 comments

Comments

@joacar
Copy link

joacar commented Jan 12, 2017

This is a rather broad question that might be the result of me just doing it wrong :) Anyhow

The case

This is the model

public class Customer {
// Properties removed for brevity
public ICollection<Contact> Contacts { get; set; } = new List<Contact>() { new Contact() };
}

public class Contact
{
[Required]
public string Name {get;set;}
public string Email {get;set;}
public string Phone {get;set;}
}

I have a UI where the user can add contacts dynamically (if they wish). So either no contacts, one or many valid contacts (name and either of email or phone)

The problem

I initate the collection with an empty object that is used to generate the editor template (@Html.EditorFor(m => m.Contacts[0]). This ensures the correct name and id attribute generation is used.

If the user doesn't want to add any new contacts, the default contact is serialized and sent to the controller where validation fails.

Solution or workaround

  1. What would be nice to have is an [OptionalCollection] attribute that, if the contained object is the default, removes it from the collection.

  2. It would also be possible to place the default Contact as a property on Customer and use it to generate the editor template (this would not generate correct attributes but that could be solved on the client side). But doing this would require a [SkipValidation] attribute

  3. Another option would be add a data-skip="true" on the input types that should not be sent to the server. This would off course require the submission of the form to pass through client side processing

Thanks

#5642

@frankabbruzzese
Copy link

You are on a wrong path! You dont need a fake object to create an editor tempate for a new item to add in a collection. There are several way to face the problems, but basically you use a standard partial view with a null model. Then to fix all input field names you set VieData.TemplateInfo.HtmlPrefix="Contacts[N+1]", where N is the last index used in the collection. Then you have several options on wgere to place everything, but for sure you need JavaScript.

@joacar
Copy link
Author

joacar commented Jan 12, 2017

Probably I've been doing it wrong, but that was my first approach.

Example

public IActionResult Create() { return new View(); }

// View
@model Customer

@Html.EditorFor()

// Contacts
@Html.EditorFor(m => m.Contacts[0]) // Renders the template
@Html.EditorFor(m => m.Contacts) // Does not render template
@Html.EditorFor(m => Model.Contacts) // Does not render template

What I'm a doing wrong?

Thanks for the help

@frankabbruzzese
Copy link

frankabbruzzese commented Jan 12, 2017

  1. you must render all already inserted contacts with a foron Contacts:
@for(int i=0; i< Model.Contacts.Count; i++)
{
@Html.EditorFor(m => m.Contacts[i])
}

The you render a partial for adding a new contact.
You cant use EditorFor but you must call a partial since you need to mainipulate the partial view HtmlPrefix.
That is you create a ViewDataDictionary set its HtmlPrefix as explained in my previous post, and then pass it to @partial. This way the partial will receive an empty model, that is what you need for an insert, but at the same time input fields are rendered with the right prefix.

@joacar
Copy link
Author

joacar commented Jan 13, 2017

@frankabbruzzese Thanks for the help that lead me to a solution. I will outline it here for others that might have the same question

// PartialView _AddContact.cshtml
@model App.Features.Customer.CustomerContactModel

@{
    ViewData.TemplateInfo.HtmlFieldPrefix = "Contacts[0]";
}

@Html.EditorForModel()

// PartialView _AddContacts.cshtml
@model IList<App.Features.Customer.CustomerContactModel>

@{
    ViewData.TemplateInfo.HtmlFieldPrefix = "Contacts";
}

@Html.EditorForModel()

// Usage Edit.cshtml
@Html.Partial("_AddContact", new CustomerContactModel())

// Usage Create.cshtml
@Html.Partial("_AddContact")

// Controller action called from javascript
public IActionResult AddContact([FromForm] IList<CustomerContactModel> contacts)
        {
            if (ModelState.IsValid)
            {
                ViewData["inline"] = true; // Different template for inline edit
                return PartialView(contacts);
            }
            return new JsonResult(ModelState)
            {
                StatusCode = (int) HttpStatusCode.BadRequest
            };
        }

For some reason I've to init with an empty customer contact model in Edit.cshtml. Using command/query pattern which I've found needed a common base class to work with EditorTemplates so might be something there that differes causing the edit view to require an explicit instance

@Eilon
Copy link
Member

Eilon commented Jan 20, 2017

Closing because this question is resolved. Thanks!

@Eilon Eilon closed this as completed Jan 20, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants