I'm having an issue with a field being validated that seemingly is failing validation, but which has no requirements for being completed. I'll try to include all information, so apologies for such a lengthy question post!
The ViewModel that has been created is a DTO object which contains just enough information to action whatever data the user enters. Due to the unknown quantity of items, my view model is slightly more complex than would be ideal ...
public class UpdateViewModel
{
public UpdateViewModel()
{
WorkingPeriods = new List<WorkingPeriod>();
LeavePeriods = new List<LeavePeriod>();
}
public Guid UserID { get; set; }
public DateTime From { get; set; }
public DateTime Until { get; set; }
public DateTime Selected { get; set; }
public IEnumerable<WorkingPeriod> WorkingPeriods { get; set; }
public IEnumerable<LeavePeriod> LeavePeriods { get; set; }
public class WorkingPeriod
{
public int ID { get; set; }
public DateTime Date { get; set; }
public TimeSpan? StartTime { get; set; }
public TimeSpan? EndTime { get; set; }
}
public class LeavePeriod
{
public int ID { get; set; }
public DateTime Date { get; set; }
public int LeaveTypeID { get; set; }
public Guid? Reference { get; set; }
public string Period { get; set; }
public TimeSpan? UserDefinedPeriod { get; set; }
}
}
I've got a view that with the helper of a few static helpers is able to produce the HTML that represents the information being. The output HTML looks similar to:
<li class="editorRow">
<input type="hidden" name="LeavePeriods.index" autocomplete="off" value="8a5522c6-57fe-40a2-95bc-b9792fbe04d3" />
<input id="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__ID" name="LeavePeriods[8a5522c6-57fe-40a2-95bc-b9792fbe04d3].ID" type="hidden" value="4" />
<input id="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__Date" name="LeavePeriods[8a5522c6-57fe-40a2-95bc-b9792fbe04d3].Date" type="hidden" value="23/04/2012 00:00:00" />
<select class="leaveTypeDropdown" id="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__LeaveTypeID" name="LeavePeriods[8a5522c6-57fe-40a2-95bc-b9792fbe04d3].LeaveTypeID">
<option value="">- please select -</option>
<option selected="selected" value="2">Annual Leave</option>
<option value="34">Public Holiday</option>
<option value="1">Sickness</option>
</select>
<div class="leavePeriodRadio">
<input checked="checked" id="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__ALL" name="LeavePeriods[8a5522c6-57fe-40a2-95bc-b9792fbe04d3].Period" type="radio" value="ALL" /><label for="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__ALL">Full Day</label>
<input id="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__AM" name="LeavePeriods[8a5522c6-57fe-40a2-95bc-b9792fbe04d3].Period" type="radio" value="AM" /> <label for="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__AM">AM</label>
<input id="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__PM" name="LeavePeriods[8a5522c6-57fe-40a2-95bc-b9792fbe04d3].Period" type="radio" value="PM" /> <label for="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__PM">PM</label>
<input class="other-user-period" id="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__Other" name="LeavePeriods[8a5522c6-57fe-40a2-95bc-b9792fbe04d3].Period" type="radio" value="Other" /> <label for="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__Other">Other</label>
<span class="user-defined" style="display:none">
Duration: <input class="timepicker" id="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__UserDefinedPeriod" name="LeavePeriods[8a5522c6-57fe-40a2-95bc-b9792fbe04d3].UserDefinedPeriod" type="text" value="" />
</span>
</div>
</li>
The issue appears when a user submits the form using Ajax (set up via the Ajax.BeginForm construct) (MVC3 & JQuery are involved).
#using (Ajax.BeginForm("Index", "Timesheet", new AjaxOptions()
{
HttpMethod = "POST",
LoadingElementId = "loading",
UpdateTargetId = "contentPane",
OnComplete = "SaveCompleted",
}))
If the user omits the field (which they are perfectly entitled to do), they receive the message: The UserDefinedPeriod field is required..
Alternatively, if they enter a valid TimeSpan value (e.g. "12:00"), they get: The value '12:00' is not valid for UserDefinedPeriod..
If they enter an invalid string (e.g. "Boris"), the value is cleared and they are presented with the field required message above as if they had entered nothing. (Assumption: The value is cleared I believe because 'Boris' cannot be stored in a TimeSpan? and therefore cannot be transported back to the view)
Using breakpoints and inspecting the values of various things, I've concluded the following:
The value is present in Request.Form and can be seen with the others that are grouped with it.
The value is present within the Model and can be seen by inspecting it using the breakpoints in VS.
ModelState.IsValid is returning false because Model.LeavePeriods.UserDefinedPeriod has 1 error, which is the same as is displayed to the user in the end.
For reference, the Controller is:
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public class TimesheetController : Controller
{
[HttpPost]
public ActionResult Index(UpdateViewModel updates, User currentUser)
{
if (!ModelState.IsValid)
{
// error with model, so lets deal with it and return View();
}
// continue with parsing and saving.
}
}
The thing that confuses me the most about this issue is that I have other boxes that are built in similar ways that cause no issue - such as both of the TimeSpan?s within the WorkingPeriod enumerable. I'm running out of ideas as to where I can look to see what could be the issue. I've tried changing the ViewModel's expected value from a TimeSpan? to a normal string with no perceivable differences (even the error messages were identical).
I'm guessing that the issue lies either outside of the scope I've detailed below (though, the controller and model are relatively vanilla so I'm lost as to where else to look) or that there is something further I don't understand about model binding (and there is plenty I don't understand about model binding already!).
I've managed to solve this issue, but I'm not overly sure why what I've fixed, fixed it.
Basically, within the UpdateViewModel I changed the "UserDefinedPeriod" to be named "OtherPeriod".
public class LeavePeriod
{
public int ID { get; set; }
public DateTime Date { get; set; }
public int LeaveTypeID { get; set; }
public Guid? Reference { get; set; }
public string Period { get; set; }
public TimeSpan? OtherPeriod { get; set; }
}
Then obviously changed all code references to it elsewhere within my code, including the view:
<span class="user-defined" style="display:none">
Duration: <input class="timepicker" id="LeavePeriods_8a5522c6-57fe-40a2-95bc-b9792fbe04d3__OtherPeriod" name="LeavePeriods[8a5522c6-57fe-40a2-95bc-b9792fbe04d3].OtherPeriod" type="text" value="" />
</span>
This then seemed to fix the issue. I'm not sure what the specific reason for the change working was - so I'm open to any comments as to what may have caused it still. However, this tweak was the fix for me and may work for anyone else having this issue in the future.
Related
I am developing my ASp.net MVC application using Kendoui MVC extensions.
So In my form I have all the kendoui grid, dropdown, datepicker etc...
I have accumulated them all in my #using(Html.BeginForm ())
But I am not sure How do i post my form data to the controller. If anybody has done that could you please shre your experience.
For example:
I have two entities: Faculty and FacultyType in My DAL
public class Faculties
{
public int FacultyId { get; set; }
public string StaffNumber { get; set; }
public int FacultyTypeId { get; set; }
public virtual FacultyTypes FacultyTypes { get; set; }
}
public class FacultyTypes
{
public FacultyTypes()
{
this.Faculties = new List<Faculties>();
}
public int FacultyTypeId { get; set; }
public string FacultyTypeCode { get; set; }
public string FacultyType { get; set; }
public bool IsDefunct { get; set; }
public virtual ICollection<Faculties> Faculties { get; set; }
}
I have my ViewModels in my application:
public class ViewModelFaculty
{
public int FacultyId { get; set; }
public string StaffNumber { get; set; }
public ViewModelFacultyTypes FacultyType { get; set; }
}
public class ViewModelFacultyTypes
{
[ScaffoldColumn(false)]
public int FacultyTypeId { get; set; }
[Required(ErrorMessage ="Faculty Type Code is Required")]
public string FacultyTypeCode { get; set; }
[Required]
public string FacultyType { get; set; }
public bool IsDefunct { get; set; }
}
My View page having kendoui extensions for ASp.net MVC.
#model FMSApps.Models.ViewModelFaculty
#using(Html.BeginForm())
{
<fieldset>
<legend class="main">Personal Particulars</legend>
<div class="view-table">
<div class="view-row">
<div class="view-name"><label for="staffId">Staff ID</label></div>
<div class="view-name"><input id ="txtStaffId" name="StaffId" class="k-textbox" data-bind="value: StaffId" type="text" required /></div>
<div class="view-name"><label for="facultyType">Faculty Type</label></div>
<div class="view-name">
#(Html.Kendo().DropDownList()
.Name("ddFacultyType")
.DataTextField("FacultyType")
.DataValueField("FacultyTypeId")
.OptionLabel("Select Faculty Type")
.Enable(true)
.AutoBind(true)
.DataSource(ds =>
{
ds.Read(read =>
{
read.Action("GetFacultyTypes", "NewFaculty");
})
.ServerFiltering(true);
})
)
</div>
</div>
I am showing a part of my view as if I could post two fields then rest all will be of same logic.
So now when I submit my form I need to post the data in the form to controller.
I know it can be dne in ASp.net mvc html helpers but not sure how to do with kendoui, even kendoUi website as also not given any ened to end exaple of how to use the extensions in a form and how to post the data.
If anyone has been working in same enviorment or any direction on how to do please suggest me.
Let's say if you use
<input id ="txtStaffId" name="StaffId" class="k-textbox" data-bind="value: StaffId" type="text" required /></div>
#(Html.Kendo().DropDownList()
.Name("ddFacultyType")
.DataTextField("FacultyType")
.DataValueField("FacultyTypeId")
.OptionLabel("Select Faculty Type")
.Enable(true)
.AutoBind(true)
.DataSource(ds =>
{
ds.Read(read =>
{
read.Action("GetFacultyTypes", "NewFaculty");
})
.ServerFiltering(true);
})
)`
When a button with attribute is pressed on the page, the field on the form will then be post to the controller. Which means in this case, a field name 'txtStaffId' paired with input value & 'ddFacultyType' with the paired value(whichever you select on the list) will be send to the controller.
There is a view to update a complex model(Transaction).
Complex model has properties which can have multiple attachments(files),
so that user can upload multiple files simultaneously in this form,
and I am trying to save these files to the database.
I have successfully posted multiple files to the server,
following blog post
http://haacked.com/archive/2010/07/16/uploading-files-with-aspnetmvc.aspx.
However in order to save these files, so that I can keep track of which files belongs to which object of the complex model(Transaction) and therefore show them later at appropriate places, I need some way to associate file uploaded to the object it belongs to, but since all files come under name 'files' I don't know how I can make this work.
Here is simplified complex model:
public class Transaction
{
[Key]
public int Id { get; set; }
public virtual PurchaseRequisition PurchaseRequisition { get; set; }
public virtual Evaluation Evaluation { get; set; }
}
Properties of complex model:
public class PurchaseRequisition
{
[Key, ForeignKey("Transaction")]
public int TransactionId { get; set; }
public virtual Transaction Transaction { get; set; }
[Display(Name = "Specifications/Requisitioner's Notes")]
public virtual ICollection<Attachment> SpecsRequisitionerNotesFiles { get; set; }
}
public class Evaluation
{
[Key, ForeignKey("Transaction")]
public int TransactionId { get; set; }
public virtual Transaction Transaction { get; set; }
public virtual ICollection<Attachment> BidResultsFiles { get; set; }
}
public abstract class Attachment
{
[Key]
public int Id { get; set; }
public string FileName { get; set; }
public string FileExtension { get; set; }
public byte[] Data { get; set; }
public Boolean Deleted { get; set; }
}
Here is the controller:
[HttpPost]
public ActionResult Create(TransactionViewModel model, IEnumerable<HttpPostedFileBase> files)
{ //save to database }
Create separate sections in the view for the purchase requisitions and bid results. Something like this:
<form action="" method="post" enctype="multipart/form-data">
<h3>Purchase Requistions</h3>
<label for="file1">Filename:</label>
<input type="file" name="purchasereqs" id="file1" />
<label for="file2">Filename:</label>
<input type="file" name="purchasereqs" id="file2" />
<h3>Bid Results</h3>
<label for="file3">Filename:</label>
<input type="file" name="bidresults" id="file3" />
<label for="file4">Filename:</label>
<input type="file" name="bidresults" id="file4" />
<input type="submit" />
</form>
Then you would have an action signature like this:
[HttpPost]
public ActionResult Create(
TransactionViewModel model,
IEnumerable<HttpPostedFileBase> purchasereqs,
IEnumerable<HttpPostedFileBase> bidresults)
{
//save to database
}
I hope someone can help with this one. I have three Model classes like this:
public class Provider
{
public Guid ProviderId { get; set; }
public string Name { get; set; }
public Guid LocationId { get; set; }
public virtual Location Location { get; set; }
}
public class Location
{
public Guid LocationId { get; set; }
public string NameOrCode { get; set; }
public string Description { get; set; }
public string StreetNumber { get; set; }
public string StreetAddress1 { get; set; }
public string StreetAddress2 { get; set; }
public string City { get; set; }
public int? StateId { get; set; }
public string Zip { get; set; }
public string ContactPhone { get; set; }
public virtual State State { get; set; }
}
public class State
{
public int StateId { get; set; }
public string Name { get; set; }
public string Abbreviation { get; set; }
}
As you can see, a Provider has a Location (separate class for reuse elsewhere), and a Location has a State (which is null until selected).
My Controller looks like this for my Create methods:
public class ProviderController : BaseController
{
private SetupContext db = new SetupContext();
// other CRUD methods ...
//
// GET: /Provider/Create
public ActionResult Create()
{
Location location = new Location()
{
LocationId = Guid.NewGuid(),
NameOrCode = Resources.BillingLocation,
Description = Resources.BillingLocationDescription
};
Provider provider = new Provider()
{
ProviderId = Guid.NewGuid(),
LocationId = location.LocationId,
Location = location
};
ViewBag.StateId = new SelectList(db.States, "StateId", "Name", provider.Location.StateId);
return View(provider);
}
//
// POST: /Provider/Create
[HttpPost]
public ActionResult Create(Provider provider)
{
if (ModelState.IsValid)
{
db.Locations.Add(provider.Location);
db.Providers.Add(provider);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.StateId = new SelectList(db.States, "StateId", "Name", provider.Location.StateId);
return View(provider);
}
// other CRUD methods ...
}
Finally, my View looks like this:
<div class="editor-label">
#Html.LabelFor(model => model.Location.StateId, #Resources.Location_State_Display_Name)
</div>
<div class="editor-field">
#Html.DropDownList("StateId", #Resources.ChooseFromSelectPrompt)
#Html.ValidationMessageFor(model => model.Location.StateId)
</div>
My problem is that the state the user selects in the DropDownList never gets set on my Model on the Create POST. I have similar code in my Edit View and the state is populated correctly in that View (that is, the state associated with an existing Provider.Location shows selected in the DropDownList for the user to edit if desire), but in both the Create and the Edit Views the selection made by the user is never registered in my Model (specifically the Provider.Location.StateId) coming in from the POST.
Looking at the HTML produced I see this:
<div class="editor-label">
<label for="Location_StateId">State/Territory</label>
</div>
<div class="editor-field">
<select id="StateId" name="StateId"><option value="">[Choose]</option>
<option value="1">Alabama</option>
<option value="2">Alaska</option>
<!-- more options ... -->
</select>
<span class="field-validation-valid" data-valmsg-for="Location.StateId" data-valmsg-replace="true"></span>
</div>
I suspect I need to somehow convey the Location.StateId relationship instead of just StateId as I see above but I can't figure out the correct syntax to do that. I've tried changing my ViewBag dynamic property to Location_StateId like this:
ViewBag.Location_StateId = new SelectList(db.States, "StateId", "Name", provider.Location.StateId);
And the DropDownList in my View like this:
#Html.DropDownList("Location_StateId", #Resources.ChooseFromSelectPrompt)
I figured then perhaps that notation would work because the label beside my DropDownList was rendered as:
<div class="editor-label">
<label for="Location_StateId">State/Territory</label>
</div>
This attempt did not work. Can you help me out?
Thanks in advance.
#Html.DropDownList("Location.StateId", #Resources.ChooseFromSelectPrompt)
Also the following line doesn't do anything useful:
ViewBag.StateId = new SelectList(db.States, "StateId", "Name", provider.Location.StateId);
You are assigning a SelectList to something that is supposed to be a scalar property. You probably wanted to pass the collection as ViewBag:
ViewBag.States = new SelectList(db.States, "StateId", "Name", provider.Location.StateId);
and then in the view:
#Html.DropDownList("Location.StateId", (SelectList)ViewBag.States)
Let me be more specific in my question.
I have a many to many relationship. I have an Opportunity and I have items.
In my Opportunity/Create view I want to be able to "add" as many items from the items list as I want. So when I click the save button on the create form, I would be potentially saving many items that would be associated with this record.
So the only way to do this is to include a click box for every item in my items table? How would I do something like Click this button to insert a new item from a drop down list?
Models:
public class Opportunity
{
public int Id { get; set; }
public string MyOpportunity { get; set; }
}
public class Items
{
public int Id { get; set; }
public string ItemName { get; set; }
}
public class ItemsToOpportunity
{
public int Id { get; set; }
public int OpportunityId { get; set; }
public int ItemId { get; set; }
}
Based on your question and comment, it sounds like you just need to provide a form with multiple checkboxes or so.
Then, when you post to the server, the shared name of the checkboxes becomes a comma delimited set of Ids to use.
<input type="checkbox" name="myNewOpportunityItems" value="1"><span>Item 1</span>
<input type="checkbox" name="myNewOpportunityItems" value="2"><span>Item 1</span>
<input type="checkbox" name="myNewOpportunityItems" value="3"><span>Item 1</span>
<input type="checkbox" name="myNewOpportunityItems" value="4"><span>Item 1</span>
Then, when the user selects the checkboxes of the items they want to associate, that gets posted as a comma delimited string:
public ActionResult MyPostActionResult(string myNewOpportunityItems)
{
// myNewOpportunityItems == "1,3,4";
}
I have two models:
public class Contact
{
public Guid ContactId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Email
{
public Guid EmailId { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public string Name { get; set; }
public Guid ContactId { get; set; }
}
Is it possible with MvcScaffolding, to automatically be generated View Contacts-> Create with dynamic textbox for field email?
For example, when entering the email in the first textbox, then the following created another textbox, etc.
In your model, make the email field a List.
If you set the names as follows, the mvc model binder will automatically populate your list on postback:
<input name="Emails[0]" type="text" />
<input name="Emails[1]" type="text" />
<input name="Emails[2]" type="text" />
and so forth. I'm assuming you're using jquery or something similar to dynamically add the textboxes, so just set the new textboxes name equal to total count - 1. And if you want to allow the user to remove any one of the textboxes, you'll have to go back through and recalculate the names and their index.