Skip to content

Instantly share code, notes, and snippets.

@MFFoX
Last active August 29, 2015 14:19
Show Gist options
  • Save MFFoX/00b8b7d3bfbddbfc1107 to your computer and use it in GitHub Desktop.
Save MFFoX/00b8b7d3bfbddbfc1107 to your computer and use it in GitHub Desktop.
There are a few different strategies you can take. Please note, I am not going to go that much into the implementation, since there is a ton of high-quality content on http://asp.net and other websites. I will be focusing on the higher-level architectural decisions. When applicable, I will link to off-site resources that might shed light on implementation details.
Below I've provided an example (at the bottom of this post) that is modeled after the hypothetical needs of a pet adoption agency that lets adopters apply online. `Applicant` will be the top-level model. As you can see, this model is rather large and could possibly contain hundreds of fields of data. We also are going to have some fairly complex validation. **So, what strategies are plausible when dealing with large datasets with multiple complex properties? **
### Separate the form into multiple forms and save multiple times (current approach)
The biggest plus side of this approach is that it vastly simplifies your mark up and allows the pipeline to be broken up into manageable pieces. The downside is that your validations must be more complex to make sure the model is valid at the different states of submission.
**Submission State**
So, when we don't have to incorporate mark-up for all of this in one view, then we don't have to strategize on how to separate this data via client-side so it isn't intimidating to users. But, when we are submitting the same model in different states, that means our validation has to be respectful to the state of the model and therefore is more complex. For example, if you applied this approach and on your first post submission you are collecting `Applicant.References`, you'll have to make `Applicant.AmountOfPetsOwned` nullable (so it doesn't default to `0` and corrupt the accuracy of the data and validations) and create a Validator that understands that `Applicant.AmountOfPetsOwned` is not required _yet_. If you're using [`System.ComponentModel.DataAnnotations`](https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations(v=vs.100).aspx) validation attributes, it is impossible to use this approach because the data annotations are tightly coupled to the types with no regard for the different states your domain might need to acknowledge. You'll have to implement a validation system that is capable of decoupling validation from being type-based. If you decide to go this route, I'd suggest looking into [Fluent Validation](https://github.com/JeremySkinner/FluentValidation). You can get around this limitation by using [RuleSets](https://github.com/JeremySkinner/FluentValidation/wiki/Creating-a-Validator#rulesets). RuleSets allow you to group validation rules together which can be executed together as a group whilst ignoring other rules. For example:
public class ApplicantValidator : AbstractValidator<Applicant> {
public ApplicantValidator () {
RuleSet("InitialValidation", () => {
//must have at least 2 references
RuleFor(x => x.References).Must(references => references.Count > 1);
});
}
}
**Model Identification**
We also have to have a way to identify that this application is the same application through the different states of posting. This can be done a few ways. A popular way is to assign an `Id` (via `Applicant.Id`) on the initial creation and include the `Id` in other form submissions (via a hidden field ). The problem with this approach is that it can be edited and there's no real way to tell if it has been messed with. In systems that implement user authentication, this is less important because you can assign ownership on creation, but for a system like ours it becomes vulnerable to tampering.
### Separate the form into multiple sections and save once (recommended approach)
This can be intimidating when the model is large and complex but by utilizing a view engine, we can simplify the hard parts. Also, we don't have to worry about this pesty state thing anymore. We have two states in this, _New_ (ie the first `HttpGet` request) and _Submitted_ (ie the first and only `HttpPost`) so validation can focus on a type and not a state.
**Front-end Considerations**
To implement this approach in a reusable and navigable manner, you should utilize some key features of Razor; mainly template processing via partials. To start, you'll need to break out all of complex properties into their own partial Editor Templates. This tutorial, [ASP.NET MVC display and editor templates](http://www.growingwiththeweb.com/2012/12/aspnet-mvc-display-and-editor-templates.html#editor-templates) by Daniel Imms, covers these concepts in detail. Focus on creating a generic form for all of the different types. In this example, those types would be `Person`, `Applicant`, `Address`, and `PhoneNumber`. In `Applicant.cshtml`, you should have something like:
@model Namespace.ViewModels.Applicant
<div class='wizard'>
@using(Html.BeginForm())
{
<div class='page' id='page-1'>
Who are you?
@Html.EditorFor(model => model, "Person") <!-- pass the model to the person template
@Html.EditorFor(model => model.AmountOfPetsOwned)
</div>
<div class='page' id='page-2'>
<!-- use something like knockout to add multiple References to the form -->
</div>
}
**Pet Adoption Example Model:**
public class Applicant : Person
{
public List<Person> References { get; set; }
public int AmountOfPetsOwned { get; set; }
public Applicant()
{
this.References = new List<Person>();
}
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? BirthDate { get; set; }
public List<Address> Addresses { get; set; }
public List<PhoneNumber> PhoneNumbers { get; set; }
public Person()
{
this.Addresses = new List<Address>();
this.PhoneNumbers = new List<PhoneNumber>();
}
}
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
public string City { get; set; }
public StateType State { get; set; }
public string ZipCode { get; set; }
}
public class PhoneNumber
{
public int Id { get; set; }
public PhoneNumberType PhoneNumberType { get; set; }
public string Number { get; set; }
}
public enum StateType //shortened for brevity
{
[Description("Alabama")] AL,
[Description("Alaska")] AK,
[Description("Arkansas")] AR
}
public enum PhoneNumberType
{
Mobile, Home, Work
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment