I have a Profile page with a Postcode look up feature from https://postcodes.io. So far I have a Partial View with Ajax Form using the following code.
#using (Ajax.BeginForm("_CityLookUp", "Home", new AjaxOptions
{
HttpMethod = "POST",
UpdateTargetId = "div1",
InsertionMode = InsertionMode.Replace
}))
{
<div id="div1">
<div class="form-group">
#Html.LabelFor(m => m.PostCode, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.PostCode, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
#Html.HiddenFor(m => m.County, new { #class = "form-control", id = "County" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
#Html.HiddenFor(m => m.City, new { #class = "form-control", id = "City" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-primary" value="Look Up" />
</div>
</div>
</div>
}
This works. I then rendered the Partial Page on the main profile page with the main profile form.
I just want to assign the value from the two other hidden fields to fields in the main page.
Partial Controller
[HttpGet]
public PartialViewResult _CityLookUp()
{
return PartialView();
}
[HttpPost]
public PartialViewResult _CityLookUp(string postcode)
{
var client = new PostcodesIOClient();
var result = client.Lookup(postcode);
var jst = new AddressLookupViewModel();
jst.City = result.AdminDistrict;
jst.County = result.AdminCounty;
jst.PostCode = postcode;
ViewBag.City = jst.City;
ViewBag.County = jst.County;
var results = new BooksViewModel();
results.City = jst.City;
results.County = jst.County;
return PartialView(jst);
}
I have tried a new view model and assigning the results to the view model but it didn't work. Tried using JavaScript to get the value of the hidden field, no luck. If you'd rather do with a separate method, please explain how you would implement it.
Parent View
#model TestingView.Models.ParentViewModel
<div class="container">
#Html.Partial("_CityLookUp")
<div class="form-group">
<div class="col-md-10">
#Html.TextBoxFor(v => v.City, new { #class = "form-control", id = "City" })
</div>
</div>
</div>
PartialViewModel
namespace TestingView.Models
{
public class AddressLookupViewModel
{
public string PostCode { get; set; }
public string County { get; set; }
public string City { get; set; }
}
}
Parent View Model
namespace TestingView.Models
{
public class ParentViewModel
{
public string City { get; set; }
public string County { get; set; }
}
}
Side question: For some reason, when I hover over
#Html.HiddenFor(m => m.City, new { #class = "form-control", id = "City" })
on the parent view, it references the AddressLookUpViewModel and not the BooksViewModel in the parent view.. I have added both View Models.
I will answer this two ways; first using the approach you were initially asking about and then how I would prefer to implement this.
Solution 1
With this approach we will copy the value of the City element from the partial view to the parent view.
First thing we need to fix up is when you view the profile right now, there will be two elements with an Id of "City"; one from the parent page and one from the partial. That's invalid markup and it will cause problems for any potential solution.
Rename the Id attribute in the parent view:
#Html.TextBoxFor(v => v.City, new { #class = "form-control", id = "Profile_City" })
Update the partial view to call a js function when it successfully retreives a set of postcode values:
#using (Ajax.BeginForm("_CityLookUp", "Home", new AjaxOptions
{
HttpMethod = "POST",
UpdateTargetId = "div1",
InsertionMode = InsertionMode.Replace,
OnSuccess = "UpdateProfile" // Add this
}))
Add a js function, UpdateProfile, to the end of the parent view:
#section scripts {
<script>
function UpdateProfile()
{
var City = $("#City").val();
alert(City);
$("#Profile_City").val(City);
}
</script>
}
That should be all that's required. The alert is just there for debugging.
The #section scripts code will be injected in to your _Layout.cshtml where it calls #RenderSection("scripts", required: false), part of the default MVC project.
One problem that might crop up going forward is when you build the parent view into a form you might be tempted to nest the form elements for layout reasons but nested form elements aren't permitted.
Solution 2
This approach uses jQuery's ajax() method to fetch the data and directly populate the relevant fields on a form.
Set up the model.
namespace TestingView.Models
{
public class ProfileViewModel
{
public string PostCode { get; set; }
public string County { get; set; }
public string City { get; set; }
}
}
This is a copy of AddressLookupViewModel as it contains all the necessary fields. I have simply renamed it to suggest its use is for the main profile form itself.
Create the view.
The view now has a single Html.BeingForm(), with the Look Up button bound to an ajax function rather than submitting an ajax form.
Its not 100% clear to me whether you want the user to be able to edit the County and City fields after a look up. In the code below they can.
#model TestingView.Models.ProfileViewModel
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(m => m.PostCode, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.PostCode, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="button" class="btn btn-primary" onclick="Lookup()">Look Up</button>
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.County, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.County, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.City, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.City, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-primary" value="Submit" />
</div>
</div>
}
#section scripts {
<script>
function Lookup() {
$.ajax("/Home/CityLookup", {
type: "POST",
dataType: 'json',
data: {
postcode: $("#PostCode").val()
}
})
.success(function (response) {
console.log("Success");
console.log(response);
var cityObj = response;
$("#City").val(cityObj.City);
console.log("City " + cityObj.City);
$("#County").val(cityObj.County);
})
.error(function () {
alert("There was a problem looking up the postcode");
})
};
</script>
}
Create a controller to service the ajax request. This is heavily based on your POST _CityLookUp controller. Note the Action type is JsonResult and the return converts the jst object in to a JSON string. Using JSON makes the lookup extensible; we can return multiple properties as an object and unpack them to a javascript object for use client-side.
[HttpPost]
public JsonResult CityLookup(string postcode)
{
var client = new PostcodesIOClient();
var result = client.Lookup(postcode);
var jst = new AddressLookupViewModel();
jst.City = result.AdminDistrict;
jst.County = result.AdminCounty;
jst.PostCode = postcode;
return Json(jst);
}
Related
When i try to load partial view directly in browser and submit it displaying the error messages but if that same partial view is in another view it is not showing that model error validations. to load partial view inside the view i'm using Ajax.Beginform Method.
When Loading partial view in browser
When including inside the another view
My Partial View
#model FPW.FPWClientModels.SiteClientModel
#if (this.ViewContext.FormContext == null)
{
this.ViewContext.FormContext = new FormContext();
}
#using (Ajax.BeginForm("CreateSite", "Site", null, new AjaxOptions
{
HttpMethod = "POST",
AllowCache = false,
////LoadingElementId = "AjaxOverlay",
//OnSuccess = "SiteOnSaveSuccess",
//OnFailure = "SiteOnSaveFailure",
}, new { #id = "SiteCreateForm" }))
{
<div class="modal-body">
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.SiteName, htmlAttributes: new { #class = "control-label" })
<div class="col-md-12">
#Html.EditorFor(model => model.SiteName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SiteName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SiteAddress, htmlAttributes: new { #class = "control-label" })
<div class="col-md-12">
#Html.EditorFor(model => model.SiteAddress, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SiteAddress, "", new { #class = "text-danger" })
</div>
</div>
<div class="modal-footer">
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<script>
function SiteOnSaveSuccess(resp) {
}
function SiteOnSaveFailure(resp) {
}
</script>
My Controller
public ActionResult CreateSite()
{
SiteClientModel oSiteClientModel = new SiteClientModel();
return PartialView(oSiteClientModel);
}
//Create Site
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateSite(SiteClientModel oSiteClientModel)
{
if (ModelState.IsValid)
{
var saveSiteDetails = oSiteApiController.CreateSiteDetails(oSiteClientModel);
return PartialView(saveSiteDetails);
}
else
{
oSiteClientModel.ReturnValue = new ReturnValue { IsSuccess = false, ReturnType = ReturnType.Error, Message = "Form Not Valid" };
}
return PartialView("CreateSite",oSiteClientModel);
}
Found the error. It was not used Update target id in ajax option. After use UpdateTargetId and pointed it to the form id it is working fine as expected
I used Asp.netMVC and created a view but CreateView page does not get styles and does not send parameters to the database but EditView and DeleteView work.
View Code
Iadded CkEditors code under the csHtmls code
#model My_E_Shop.Models.Pages
#{
ViewBag.Title = "Create" + Server.HtmlDecode(Html.DisplayNameFor(model => model).ToString());
}
<h2>Create#Html.DisplayNameFor(model => model)</h2>
#using (Html.BeginForm("Create", "Pages", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.PageID)
#Html.HiddenFor(model => model.CreateDate)
#Html.HiddenFor(model => model.PageSee)
<div class="form-group">
#Html.LabelFor(model => model.PageTitle, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PageTitle)
#Html.ValidationMessageFor(model => model.PageTitle)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ShortDescription, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ShortDescription)
#Html.ValidationMessageFor(model => model.ShortDescription)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PageText, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PageText)
#Html.ValidationMessageFor(model => model.PageText)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ImageName, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.Kendo().Upload().Name("imgUp")
#Html.ValidationMessageFor(model => model.ImageName)
</div>
</div>
<div class="form-group" id="buttons">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-primary" />
#Html.ActionLink("Back", "Index")
</div>
</div>
</div>
}
#section Scripts
{
<script src="/ckeditor/ckeditor.js"></script>
<script src="/ckeditor/adapters/jquery.js"></script>
<script>
$(function () {
$('#PageText').ckeditor();
});
</script>
}
Controller Code
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "PageID,PageTitle,ShortDescription,PageText")]
Pages pages, HttpPostedFileBase imgUp)
{
if (ModelState.IsValid)
{
if (imgUp != null)
{
if (CheckContentImage.IsImage(imgUp))
{
pages.ImageName = Guid.NewGuid().ToString().Replace("-", "") +
Path.GetExtension
(imgUp.FileName);
imgUp.SaveAs(Server.MapPath("/PageImages/" + pages.ImageName));
}
}
pages.PageSee = 0;
pages.CreateDate = DateTime.Now;
db.Pages.Add(pages);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(pages);
}
Please check in chrome browsers developer tool all required CSS are loaded or not under source section
You may create a ViewModel class or modify your Pages class by adding HttpPostedFileBase:
public class Pages
{
public string PageTitle{ get; set; }
// ... other fields here ...
public HttpPostedFileBase imgUp { get; set; }
}
And, in the Controller:
[HttpPost]
public ActionResult Create(Pages pages)
{
// ...
if (pages.imgUp != null)
{
// ... your code here
}
// the rest of code
}
However, if you prefer not adding HttpPostedFileBase to the model class,
you may try to handle submit via Ajax. For instance:
var formData = new FormData();
formData.append( 'file', input.files[0] );
$.ajax({
url: '/Pages/Create',
data: formData ,
dataType: 'json',
processData: false,
contentType: false,
type: 'POST',
success: function(data){
alert(data);
}
For more explanation, you're encouraged to read my answer on this thread: https://stackoverflow.com/a/58354204/4687359
Hope this helped ... :)
Finally, if you were to upload multiple files, modify the model class accordingly:
public List<HttpPostedFileBase> imgUp { get; set; }
more clarification here: https://stackoverflow.com/a/21677152/4687359
I have a viewmodel that contains a list of another viewmodel. As demonstrated here:
public class PrizeViewModel
{
public Guid PrizeId { get; set; }
public string Description { get; set; }
public List<CategoryViewModel> Categories { get; set; }
}
CategoryViewModel is defined as such:
[Serializable]
public class CategoryViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public bool Selected { get; set; }
}
The Prize view looks like this:
#model PrizeViewModel
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Prize</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Categories, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
Create New Category
#Html.EditorFor(model => model.Categories, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
I then have a CategoryEditorTemplate:
#model CategoryViewModel
<div>
<span><input id="#Model.Id" name="#Model.Id" type="checkbox" checked="#(Model.Selected)" value="#Model.Name" /></span>
<label for="#Model.Id">#Model.Name</label>
</div>
The Create method in the controller takes a PrizeViewModel, problem that I am having is that when I get the PrizeViewModel back, Categories is null. Any suggestions?
First, I don't understand how your Category template is supposed to work. You're mixing up your Boolean and id and somehow expecting them to bind... Here's how I think you want it to work.
Change your category editor template to this (it should be called CategoryViewModel.cshtml) The key is that you need to hidden values in order to post them back to the server. And, like Stephen mentions, you were overriding the Editor Template automatic collection naming by not using a helper for your input fields.
#model CategoryViewModel
<div>
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.Name)
<Label>#Html.CheckBoxFor(x => x.Selected) #Model.Name</label>
</div>
Your prize view should be fine exactly as you posted it.
Do not. I repeat, do NOT use any form of foreach or for statement with an editor template and collections.
If you loop through categories with for loop when posting to actionresult it will be able to bind model to list here is post about binding model to list
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
here is example of how to make it:
#for(int i = 0; i < Model.Categories.Count(); i++)
{
#Html.EditorFor(model => Model.Categories[i])
}
In your CategoryEditorTemplate, you are overriding the default naming required for correct binding when you do ...name="#Model.Id"...
The html for your categories should look like
<input type="text" name=Categories[0].Name ...
<input type="checkbox" name=Categories[0].Selected...
<input type="text" name=Categories[1].Name ...
....
Either use helpers in your Template, for example
#Html.TextBoxFor(m => m.Name)
#Html.CheckBoxFor(m => m.Selected)
or delete the template and use a for loop to generate the html
#for(int i = 0; i < Model.Categories.Count; i++)
{
#Html.TextBoxFor(m => m.Categories[i].Name)
#Html.CheckBoxFor(m => m.Categories[i].Selected)
}
simnilar to the answer of this question
Html.BeginForm with html attributes asp.net mvc4
I have a viewmodel for a view that contains collections that are used to populate drop downs and lists. so i dont watn to return them, i just want to return the model object. Well actually i just want to return 4 fields in that model - but that's the next problem.
I've dodged that rpeviously by doing this appraoch but im having no luck unless i submit the entire viewmodel which on this form is ridiculous as 95% of info is discarded.
Anyway the problem i get here is that i cannot get the game event that is returned in the create post to be anything other than null. The gameEvent parameter on create is NULL.
Also kinda suprised i haven't been able to find a ton of info on this.
The controller:
public ActionResult Create()
{
...
var createEventViewModel = new CreateEventViewModel()
{
Places = places,
Characters = characters,
Event = new GameEvent()
};
return this.View(createEventViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name,Description,EventType,Duration")] GameEvent gameEvent)
{
...
}
The View:
#model Sisyphus.Web.Models.CreateEventViewModel
#{
ViewBag.Title = "Create Event";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create Event</h2>
<div class="row">
<div class="col-lg-8">
<section id="createEvent">
#using (Html.BeginForm("Create", "Event",
new
{
GameEvent = Model.Event
}, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.Event.Name, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Event.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Event.Description, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextAreaFor(m => m.Event.Description, 10, 30, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Event.Duration, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Event.Duration, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Event.Duration, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Event.EventType, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EnumDropDownListFor(m => m.Event.EventType)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create Event" class="btn btn-default" />
</div>
</div>
}
</section>
</div>
</div>
The Model:
public class GameEvent
{
public string Name { get; set; }
public string Description { get; set; }
public int Duration { get; set; }
public EventType EventType { get; set; }
}
The viewmodel: (edited down have removed members that are irrelevant
public class CreateEventViewModel
{
public GameEvent Event { get; set; }
}
Edit:
Ok i just tried this
#using (Html.BeginForm("Create", "Event",
new RouteValueDictionary()
{
{"GameEvent", Model.Event}
}, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
Game event is now not null (All values in it are) - so not really any closer
Your inputs for postback are based on class CreateEventViewModel, for example
#Html.TextBoxFor(m => m.Event.Name, ...
#Html.TextAreaFor(m => m.Event.Description, ...
which would generate the following html
<input id="Event_Name" name="Event.Name" value=....
However the parameter of your post action method is typeof GameEvent, not CreateEventViewModel. If you inspect the Response.Form.Keys you will see Event.Name, Event.Description etc, but class GameEvent has properties Name, Description etc so the values cant be matched up by the ModelBinder
You need to change your post method to
public ActionResult Create(CreateEventViewModel model)
{
GameEvent event = model.GameEvent;
// do whatever with GameEvent
You should also remove new {GameEvent = Model.Event} from theHtml.BeginForm` method
Note I excluded the BindAttibute because I don't think its necessary in this case - you appear to want all the properties of GameEvent, and unless you create inputs for properties of Places and Characters, they will be null anyway, and since you are not accessing the other properties there is no mass assignment vulnerability.
Other alternative are to create the inputs manually so that the properties are correctly mapped, either direct html
<input name="Name" value=#Model.Event.Name />
<input name="Description" value=#Model.Event.Desciption />
or using helpers
var Description = Model.Event.Description;
#Html.TextBoxFor(m => Description)
My resister works perfectly when passing back the form, but one that I built it passes back nothing. I am not sure what I am doing differently than the register page that visual studio provided. It calls the right http post method but the value passed is null. thanks for any help. I just want that model to post back to the controller action result.
Controller
[HttpGet]
public ActionResult Support()
{
ViewBag.Message = "Your app description page.";
return View();
}
[AllowAnonymous]
[HttpPost, ValidateSpamPrevention]
public ActionResult Support(QuickEmailModel email)
{
if (ModelState.IsValid)
{
return View("thankyou", email);
}
return View(email);
}
Model
public class QuickEmailModel
{
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
[Required]
public string Subject { get; set; }
[Required]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Display(Name = "Company (Optional):")]
public string Company { get; set; }
}
}
Top of View
#using PoliteCaptcha
#model Models.QuickEmailModel
#{
ViewBag.Title = "Support";
}
Bottom where the form is
#using (Html.BeginForm())
{
#Html.ValidationSummary()
<div class="control-group">
<label class="control-label">
#Html.LabelFor(x => x.Company, "Company (Optional):", new { #class = "control-label" })</label>
<div class="controls">
#Html.TextBoxFor(x => x.Company, new { #class = "span4" })
</div>
</div>
<div class="control-group">
#Html.LabelFor(x => x.Email, "Email:", new { #class = "control-label" })
<div class="controls">
#Html.TextBoxFor(x => x.Email, new { #Class = "span4" })
</div>
</div>
<div class="control-group">
<label class="control-label">
#Html.LabelFor(x => x.Subject, "Subject:", new { #class = "control-label" })</label>
<div class="controls">
#Html.TextBoxFor(x => x.Subject, new { #class = "span4" })
</div>
</div>
<div class="control-group">
<label class="control-label">
#Html.LabelFor(x => x.Subject, "Description:", new { #class = "control-label" })</label>
<div class="controls">
#Html.TextAreaFor(x => x.Description, new { #class = "span4", #rows = "6", id = "txtDescription" })
</div>
</div>
#Html.SpamPreventionFields()
<input type="submit" class="btn btn-success" id="btnSubmit" value="Submit" style="margin-right: 15em;
float: right;" />
}
I believe the problem is that your parameter name is the same as one of your field names (email). Rename the Method parameter name to model (or something else besides email).
What I believe is happening is that the MVC model binder sees a posted value named "email" and it's trying to assign it to the parameter of the same name, but since it's not a string it can't do it. Thus it assigns a null.
Also, model binding is case insensitive, so Email and email are the same.
The problem is that the model mapper cannot map form name/value pairs from the POST request to an instance of QuickEmailModel object, and simply ignores them. Examine the POST request being sent in Firebug or other tool, what are the names of name/value pairs?
You should probably change your view like so:
#{ var email = ....; }
<label class="control-label">
#Html.LabelFor(x => email.Company, "Company (Optional):", new { #class = "control-label" })
</label>
<div class="controls">
#Html.TextBoxFor(x => email.Company, new { #class = "span4" })
</div>
because the name of your POST action param is "email". I think that this should force form param names to be "email.Company", etc. If not, try the other variant
#Html.TextBox("email.Company", model.Company)