MVC3 - When using HttpPostedFileBase with large files, RedirectToAction is very slow - performance

Okay, I have a situation that seems to make no sense. I have a controller thus:
public ActionResult Index()
{
return View(_courseService.ListAllCourses());
}
[HttpPost]
public ActionResult CreateNewCourse(CourseVDO course, HttpPostedFileBase CourseDataFile)
{
return RedirectToAction("Index");
}
And a View thus:
#using (Html.BeginForm("CreateNewCourse", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(false)
<fieldset>
<legend>Course</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
Course Data File
</div>
<div class="editor-field">
<input type="file" name="CourseDataFile" id="CourseDataFile" />
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Visible)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Visible)
#Html.ValidationMessageFor(model => model.Visible)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
When I submit a file of around 200KB, it uploads to the server fast enough (it's local after all), but then takes 5 seconds to go from the "return RedirectToAction("Index"); " line back to the breakpoint on the "return View(_courseService.ListAllCourses());" line (not actually executing the ListAllCourses). This means it's entirely down to the internal plumbing. Worse, this delay scales with file size. What on earth is going on, and how can I stop it?
Thanks

I have never used that method before, and this is no direct answer, but maybe its a better solution:
// used when editing an item
public void UploadFiles(FormCollection form, NameValueCollection currentFiles, string folder, bool useTicks)
{
foreach (string file in Request.Files)
{
var hpf = Request.Files[file];
if (hpf.ContentLength == 0)
{
form[file] = currentFiles[file];
}
else
{
var filename = useTicks ? hpf.FileName
.Replace(" ", "_")
.Replace(".", RandomFileName() + ".") : hpf.FileName;
var myPath = Server.MapPath("~/Content/" + folder);
hpf.SaveAs(myPath + "/" + filename);
form[file] = filename;
}
}
if (Request.Files.Count > 0) return;
foreach (var file in currentFiles.AllKeys)
{
form[file] = currentFiles[file];
}
}
//used when creating a new item
public void UploadFiles(FormCollection form, string folder, bool useTicks)
{
foreach (string file in Request.Files)
{
var hpf = Request.Files[file];
if (hpf.ContentLength == 0)
{
form[file] = null;
}
else
{
var filename = "";
filename = useTicks ?
hpf.FileName.Replace(" ", "_").Replace(".", RandomFileName() + ".") :
hpf.FileName;
UploadFileName = filename;
var myPath = Server.MapPath("~/Content/" + folder);
hpf.SaveAs(myPath + "/" + filename);
form[file] = UploadFileName;
}
}
}
I use models so in my model item i use the UIHint("uploadbox")
here is the code inside views/Shared/EditorTemplates/UploadField.cshtml
#Html.TextBox("",null,new{type="File"})
here is an example of the usage of the upload feature:
public ActionResult AddFiles(FormCollection form, SomeModel myModel)
{
UploadFiles(form,"products", true);
myModel.pdfFile = form["pdffile"];
myModel.thumbnail = form["thumbnail"];
here is the code when editing the item, in case the file was not changed, but others items have
var existingFile = ctx2.modelname.SingleOrDefault(x => x.Id == id).Filename;
NameValueCollection myCol = new NameValueCollection();
myCol.Add("Filename", existingFile);
UploadFiles(form, myCol, "uploads/custom", true);
myModel.Filename = form["Filename"];
just a thought :-)

Related

Unable to retrieve the hidden element in partial view from main view

I have implemented kendo combobox on my MVC5 view and want the combobox to be filtered based on the value in my model. I need to retrieve the value from my model. I have currently bound that value to a hidden field called CountryCode in my partial view. The script is in my main view. I am getting error message undefined while trying to access the hidden field. The model is definitely getting populated with the CountryCode.
#using System.Collections
#model CC.GRP.MCRequest.ViewModels.RequestStatusUpdateViewModel
#{
Layout = null;
}
<div class="k-popup-edit-form k-window-content k-content" >
<div class="k-edit-form-container">
#Html.HiddenFor(model => model.CountryCode)
<div class="editor-label">
#Html.LabelFor(model => model.RequestID)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.RequestID, new { htmlAttributes = new { #readonly = "readonly" } })
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ProjectName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ProjectName, new { htmlAttributes = new { #readonly = "readonly" } })
</div>
<div class="editor-label">
#Html.LabelFor(model => model.RequestStatus)
</div>
<div class="editor-field">
#(Html.Kendo().ComboBoxFor(model => model.RequestStatusCode)
.HtmlAttributes(new { style = "width:100%" })
.DataTextField("Status")
.Placeholder("Select...")
.DataValueField("RequestStatusCode")
.AutoBind(false)
.Filter("contains")
.DataSource(dataSource => dataSource
.Read(read =>
{
read.Action("GetRequestStatus", "Request")
.Type(HttpVerbs.Post)
.Data("GetCountryCodeFilter");
}).ServerFiltering(true)
)
)
</div>
#Html.ValidationMessageFor(model => model.RequestStatusCode, "", new { #class = "text-danger" })
</div>
</div>
Controller method that populates the combo
public ActionResult GetRequestStatus(string countryCode)
{
var response = requestRepository.GetRequestStatus(countryCode).AsQueryable().ProjectTo<RequestStatusViewModel>();
var jsonResult = Json(response, JsonRequestBehavior.AllowGet);
jsonResult.MaxJsonLength = int.MaxValue;
return jsonResult;
}
Controller method that loads the view
public ActionResult RequestStatus(int requestId, string projectName, string countryCode)
{
RequestStatusUpdateViewModel reqeustStatusUpdateViewModel = new RequestStatusUpdateViewModel();
reqeustStatusUpdateViewModel.RequestID = requestId;
reqeustStatusUpdateViewModel.ProjectName = projectName;
reqeustStatusUpdateViewModel.CountryCode = countryCode;
if (!ModelState.IsValid)
{
// return View("NewRequestView", Mapper.Map<RequestStatusViewModel>(newReqeustViewModel));
return null;
}
return View("_RequestStatusView", Mapper.Map<RequestStatusUpdateViewModel>(reqeustStatusUpdateViewModel));
}
RequestStatusViewModel
public class RequestStatusViewModel
{
public string RequestStatusCode { get; set; }
public string Status { get; set; }
public int DisplaySequenceNo { get; set; }
}
Script in the main view
function GetCountryCodeFilter() {
alert("Hello");
alert($('#CountryCode').val());
return { countryCode: $('#CountryCode').val() }
};
I'm assuming this is a popup from a kendo grid. The problem is that view gets serialized and sent to the popup when any row is clicked. It doesn't bind the data like you expect in MVC - it sends the same serialized data every time. See here.
So change your hidden to use kendo's MVVM binding so each instance gets the value from the grid row. (CountryCode needs to exist in the grid's datasource):
#Html.HiddenFor(x => x.CountryCode, new { data_bind = "value: CountryCode" }) // underscore becomes dash
Or you could just use:
<input type="hidden" name="CountryCode" id="CountryCode" data-bind = "value: CountryCode"/>

how to save items of cascading drop down list in database with asp.net mvc

My Drop-Downs works well the problem is when I want to save my form.
here is my controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,TourId,StartDate,EndDate")] TourDate tourDate)
{
if (ModelState.IsValid)
{
db.TourDates.Add(tourDate);
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
ViewBag.TourId = new SelectList(db.Tours, "Id", "TourName", tourDate.TourId);
return RedirectToAction("Index", "test");
}
[HttpPost]
public JsonResult GetT(int? id)
{
var tours = db.Tours.Where(e => e.CountryId == id).ToList()
.Select(e => new
{
Id = e.Id,
TourName= e.TourName
}).ToList();
return Json(tours);
}
and here is My form. in this From, selecttag with id=TourId is filling with data in ajax from the outer drop down dropdownId and it works fine
#Html.DropDownList("dropdownId",
Model.Countries.Select(m => new SelectListItem
{
Value = m.Id.ToString(),
Text = m.CountryName
}),
new { #class = "form-control" })
#using (Html.BeginForm("Create", "test"))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>TourDate</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.TourDate.TourId, "TourId", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<select class="form-control" id="TourId"></select>
#Html.ValidationMessageFor(model => model.TourDate.TourId, "", new { #class = "text-danger" })
</div>
</div>
<br />
<div class="form-group">
#Html.LabelFor(model => model.TourDate.StartDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.TourDate.StartDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.TourDate.StartDate, "", new { #class = "text-danger" })
</div>
</div>
<br />
<div class="form-group">
#Html.LabelFor(model => model.TourDate.EndDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.TourDate.EndDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.TourDate.EndDate, "", new { #class = "text-danger" })
</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>
}
the problem is when I submit the form no diffrent which Tour is chosen always there is TourId=0.
Appreciate any help and also here is the ajax if needed
$("#dropdownId").change(function () {
$('#TourId').empty();
var countrySelected = $(this).val();
$.ajax
({
url: '/test/GetT/' + countrySelected,
type: 'POST',
data: {
'countryId': countrySelected
},
success: function (data)
{
var $select = $('#TourId');
for (var i = 0; i < data.length; i++)
{
$('<option/>').attr('value', data[i].Id).html(data[i].TourName).appendTo('#TourId');
}
}
});
});
The reason your second <select> element does not submit a value is because it does not have a name attribute. It would need to be
<select class="form-control" name="TourDate.TourId" id="TourId"></select>
However there are multiple other errors and problems with your code. To note just a couple of them:
The model in your view is is not typeof TourDate (its a model
containing a property which is TourDate) so none of your controls
can be bound to your TourDate tourDate parameter in the POST
method (the model in the parameter needs to be the same as the model
in the view, or you need to use the [Bind(Prefix = "TourDate")]
attribute and you also need to remove the [Bind(Include = "..")]
attribute).
Your not getting client side validation associated with your
dropdownlists and in the POST method, if ModelState is invalid,
your just redirecting (the user would just assume the object has
been saved and not understand what is going on). You need to return
the view so errors can be corrected, but in your case, the values
the user has entered will be reset to the defaults (an annoying user
experience).
Your editing data, so you should always use a view model and in the controller method you need to populate the SelectList's to account for initial values and edited values (for when you need to return the view). You code should be
public class TourDateVM
{
[Required(ErrorMessage = "Please select a country")]
[Display(Name = "Country")]
public int? SelectedCountry { get; set; }
[Required(ErrorMessage = "Please select a tour")]
[Display(Name = "Country")]
public int? SelectedTour { get; set; }
[Required(ErrorMessage = "Please enter a start date")]
[Display(Name = "Start date")]
public DateTime? StartDate { get; set; }
.... // ditto above for EndDate
public IEnumerable<SelectListItem> CountryList { get; set; }
public IEnumerable<SelectListItem> TourList { get; set; }
}
And in the controller
public ActionResult Create()
{
TourDateVM model = new TourDateVM();
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(TourDateVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
TourDate tour = new TourDate()
{
TourId = model.SelectedTour,
StartDate = model.StartDate,
EndDate= model.EndDate
};
db.TourDates.Add(tour);
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
private ConfigureViewModel(TourDateVM model)
{
var counties = db.Countries;
model.CountryList = new SelectList(counties, "ID", "Name"); // adjust to suit your property names
if (model.SelectedCountry.HasValue)
{
var tours = db.Tours.Where(e => e.CountryId == model.SelectedCountry);
model.TourList = new SelectList(tours, "Id", "TourName");
}
else
{
model.TourList = new SelectList(Enumerable.Empty<SelectListItem>());
}
}
and finally in the view (note that both dropdownlists need to be inside the <form> element)
#model TourDateVM
....
#using Html.BeginForm())
{
....
<div class="form-group">
#Html.LabelFor(m => m.SelectedCountry, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.SelectedCountry, Model.CountryList, "- Please select -", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.SelectedCountry, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.SelectedTour, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.SelectedTour, Model.TourList, "- Please select -", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.SelectedTour, "", new { #class = "text-danger" })
</div>
</div>
....
}
and the script should be
var url = '#Url.Action("GetT")'; // assumes its in the same controller
var tours = $('#SelectedTour');
$('#SelectedCountry').change(function() {
tours.empty();
$.post(url , { id: $(this).val() }, function(data) {
if (!data) {
return;
}
tours.append($('<option></option>').val('').text('- Please select -'));
$.each(data, function(index, item) {
tours.append($('<option></option>').val(item.Id).text(item.TourName));
});
});
})

file upload does not work in IE 8, IE 9

i have the following code to upload a file, which runs without error in mozilla firefox and google chrome but gives error in IE 8. Please provide solution. This is my view-
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<fieldset>
<div class="input_panelleft">
<div class="input_panellabel">
#Html.LabelFor(model => model.DrawingName)
</div>
<div class="input_panelinput">
#Html.EditorFor(model => model.DrawingName)
</div>
<div class="input_panellabel">
Upload Drawing
</div>
<div class="input_panelinput">
<input type="file" name='file' id='file' />
</div>
<div class="center">
<input type="submit" value="Upload" />
</div>
</div>
</fieldset>
}
and following code in controller-
[HttpPost]
public ActionResult Create(DrawingDocumentsModel model, HttpPostedFileBase file)
{
if (ModelState.IsValid)
{
if (file != null)
{
var documentNameParts = file.FileName.Split('.');
var documentExtension = documentNameParts.Last();
var documentNameOnly = String.Join(".", documentNameParts.Take(documentNameParts.Length - 1));
var filename = documentNameOnly + "." + documentExtension;
if (_drawingDocumentService.IsDrawingNameAvailable(filename))
{
var path = Server.MapPath("~/Uploads/");
var Filepath = Path.Combine(path, filename);
file.SaveAs(Filepath);
var drawingDocuments = new DrawingDocuments();
drawingDocuments.ProjectId = 1;
drawingDocuments.ProjectName = "Drawing Tag";
drawingDocuments.FileName = filename;
drawingDocuments.UploadedDate = System.DateTime.Now;
drawingDocuments.UploadedBy = _workContext.CurrentUserId;
if (model.DrawingName == "" || model.DrawingName == null)
{
ModelState.AddModelError("CustomError", "Please Enter Drawing Name");
}
else
{
drawingDocuments.DrawingName = model.DrawingName;
drawingDocuments.Path = ("/Uploads/" + filename);
_drawingDocumentService.InsertDrawingDocument(drawingDocuments);
return RedirectToAction("DrawingDocuments");
}
}
else
{
ModelState.AddModelError("CustomError", "Drawing With Same File Name Already Available");
}
}
else
{
ModelState.AddModelError("CustomError", "Please Select Drawing");
}
}
return View(model);
}

Entity framework update shows dbconcurrency while updating a record?

in my code the update takes place both in the view and in the controller. Same Controller and View for both create and Update. Create profile works fine. but while updating it shows DbConcurrency exception Message. Please help me to find.
Exception Message while updating the record:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
My Update profile page has (Profile.cshtml)
#model Sitecss.Models.Profile
#using Microsoft.Web.Helpers;
#{
ViewBag.Title = "CreateProfile";
Layout = "~/Views/Shared/_HomeLay.cshtml";
}
#{
var GT = new SelectList(new[] {
new {ID ="Team DeathMatch", Name="Team DeathMatch"},
new {ID ="Search & Destroy", Name="Search & Destroy"},
new {ID ="Flag Runner", Name="Flag Runner"},
new {ID ="Domination", Name="Domination"},
new {ID ="Kill Confirmed", Name="Kill Confirmed"}
},"ID","Name");
var Spec = new SelectList(new []{
new {ID ="Assault", Name="Assault"},
new {ID ="Tactical", Name="Tactical"},
new {ID ="Long-Range Eliminations", Name="Long-Range Eliminations"},
new {ID ="Noob", Name="Noob"}
}, "ID", "Name");
}
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm("CreateProfile", "Home", FormMethod.Post, new { #encType = "multipart/form-data" }))
{
<div id="content1">
#Html.ValidationSummary(true)
<h4>Create a Profile</h4>
<div>
#Html.LabelFor(m => m.SteamName)
</div>
<div>
#Html.TextBoxFor(m => m.SteamName, new { #class = "wide" })
#Html.ValidationMessageFor(m => m.SteamName)
</div>
<div>
#Html.LabelFor(m =>m.UserImg)
</div>
<div>
<input type="file" name="image" class="image" /> <br />
</div>
<div>
#Html.LabelFor(m => m.GameType)
</div>
<div>
#Html.DropDownListFor(m => m.GameType, GT, new { #class = "wide" })
#Html.ValidationMessageFor(m => m.GameType)
</div>
<div>
#Html.LabelFor(m => m.Specialist)
</div>
<div>
#Html.DropDownListFor(m => m.Specialist, Spec, new { #class = "wide" })
#Html.ValidationMessageFor(m => m.Specialist)
</div>
<div>
#Html.LabelFor(m => m.FavGun)
</div>
<div>
#Html.TextBoxFor(m => m.FavGun, new { #class = "wide" })
#Html.ValidationMessageFor(m => m.FavGun)
</div>
<p><input type="submit" class="button" value="Ok" /></p>
</div>
}
while my controller functions are
public ActionResult CreateProfile()
{
if (db.Profiles.Any(u => u.Username == User.Identity.Name))
{
Profile pro = (from usr in db.Profiles where usr.Username == User.Identity.Name select usr).Single();
olf = pro.UserImg;
Profile p = db.Profiles.Find(pro.Id);
ViewBag.ProfilePic = olf;
return View(p);
}
else
{
ViewBag.Name = User.Identity.Name;
return View();
}
}
[HttpPost]
public ActionResult CreateProfile(Profile m)
{
WebImage photo = WebImage.GetImageFromRequest();
string newFileName = "";
string thumbs = "";
if (photo != null)
{
string ext = Path.GetExtension(photo.FileName);
newFileName = User.Identity.Name+ ext;
thumbs = #"Images/" + newFileName;
photo.Resize(width: 120, height: 120, preserveAspectRatio: true, preventEnlarge: true);
photo.Save(#"~/"+thumbs);
m.UserImg = newFileName;
}
if (ModelState.IsValid)
{
if (db.Profiles.Any(u => u.Username == User.Identity.Name))
{
db.Entry(m).State = System.Data.EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
else
{
m.Username = User.Identity.Name;
db.Profiles.Add(m);
db.SaveChanges();
return RedirectToAction("Index");
}
}
return View(m);
}
While Adding Breakpoint to the update statement in my edit Controller I came to know that the primary key Id =0 on update in order to fix this issue I have added Hidden Field in the view that has fixed the DbConcurrency on update

MVC3 - passing null item when model requires TimeSpan

Well I've recently come up against an interesting problem that I can't seem to figure out.
The error message I get is:
{"The model item passed into the dictionary is null, but this dictionary requires a non-null model item of type 'System.TimeSpan'."}
This occurs when I try to submit a new entry to the database. So, the details of what is being submitted.
The model class:
public class EventModel
{
[Key]
public int EventID { get; set; }
[DisplayName("Booking title")]
[Required(ErrorMessage="Please provide a title for the booking")]
public string Title { get; set; }
[DataType(DataType.Date)]
[DisplayName("Start date")]
[DisplayFormat(DataFormatString="{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
public DateTime StartDateTime { get; set; }
[DisplayName("End date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
[IsDateAfter("StartDateTime", true, ErrorMessage="End date must be on or after the start date")]
public DateTime EndDateTime { get; set; }
public long StartTicks { get; set; }
public long EndTicks { get; set; }
[NotMapped]
[DisplayName("Start Time")]
public TimeSpan StartTime
{
get { return TimeSpan.FromTicks(StartTicks); }
set { StartTicks = value.Ticks; }
}
[NotMapped]
[DisplayName("End Time")]
public TimeSpan EndTime
{
get { return TimeSpan.FromTicks(EndTicks); }
set { EndTicks = value.Ticks; }
}
[DefaultValue(2)]
[DisplayName("Booking is")]
public int BookingStatus { get; set; }
[DisplayName("Set recurrence")]
[DefaultValue(false)]
public bool DoesRecur { get; set; }
[DisplayName("Set recurrence type")]
public string Pattern { get; set; }
[DisplayName("Set the day this happens on ")]
public int DayIndex { get; set; }
[DisplayName("Choose the day instance this recurs on")]
public int DayCount { get; set; }
[DisplayName("Day ")]
[NotMapped]
public string Day { get; set; }
[DisplayName("Instance")]
[NotMapped]
public string Instance { get; set; }
// links resource to a user/member
[DisplayName("Booked by")]
[NotMapped]
public string BookerName { get; set; }
public Guid MemberID { get; set; }
// links resource to a resource type
[DisplayName("Resource required:")]
public int ResourceID { get; set; }
}
The action methods in the controller class:
[HttpGet]
public ActionResult Create(DateTime eventDate)
{
var days = from DayOfWeek d in Enum.GetValues(typeof(DayOfWeek))
select new { ID = (int) d, Name = (DayOfWeek)d };
var instance = from DayInstance i in Enum.GetValues(typeof(DayInstance))
select new { ID = (int) i, Name = (DayInstance)i };
MembershipUser mu = Membership.GetUser(HttpContext.Profile.UserName);
CreateEventViewModel model = new CreateEventViewModel()
{
Event = new EventModel()
{
StartDateTime = eventDate,
EndDateTime = eventDate,
MemberID = (Guid)mu.ProviderUserKey
},
Resources = DBContext.Resources.ToList(),
Patterns = DBContext.Patterns.ToList(),
ResourceTypes = DBContext.ResourceTypes.ToList()
};
ViewData["dayOfWeek"] = new SelectList(days, "ID", "Name", DayOfWeek.Monday);
ViewData["dayInstance"] = new SelectList(instance, "ID", "Name", DayInstance.First);
return View(model);
}
[HttpPost]
public ActionResult Create(CreateEventViewModel em)
{
if (ModelState.IsValid)
{
// get the resource turn aournd time
double turnAround = rc.GetResourceTurnAround(em.Event.ResourceID);
MembershipUser mu = Membership.GetUser(HttpContext.Profile.UserName);
em.Event.MemberID = (Guid) mu.ProviderUserKey;
em.Event.BookingStatus = 2;
// need to get the time added to the date.
DateTime actualStartPoint = new DateTime(em.Event.StartDateTime.Ticks + em.Event.StartTicks);
DateTime actualEndPoint = new DateTime(em.Event.EndDateTime.Ticks + em.Event.EndTicks);
em.Event.StartDateTime = actualStartPoint;
em.Event.EndDateTime = actualEndPoint;
// add turn around time to the end of the event
em.Event.EndDateTime = em.Event.EndDateTime.AddMinutes(turnAround);
// needed becase these are handled slighty differently to the rest of the model
em.Event.DayIndex = int.Parse(Request.Form.GetValues("dayOfWeek").GetValue(0).ToString());
em.Event.DayCount = int.Parse(Request.Form.GetValues("dayInstance").GetValue(0).ToString());
DBContext.Events.Add(em.Event);
DBContext.SaveChanges();
// get the resource owner
MembershipUser resourceOwner = Membership.GetUser(rc.GetResourceOwnerByID(em.Event.ResourceID));
// email the admin team and the user the details of this booking
// get the email address of the user making the booking
StringBuilder message = new StringBuilder();
message.AppendFormat("Thank you for your booking, this is now being reviewed by the team.\nThe details of your booking are included for confirmation.\n");
message.AppendFormat("Booking Title: {0}\nResource: {1}\n Date: {2} {3} (this includes our turn around time added on)\n", em.Event.Title, rc.GetResourceNameByID(em.Event.ResourceID), actualStartPoint, actualEndPoint);
message.AppendFormat("You can log in at any time to review your bookings.\nYou will receive an email when the team have reviewed this request\nMany thanks\n");
EmailHandler eh = new EmailHandler();
eh.SetRecipient(Membership.GetUser().Email);
eh.AddAdminEmail();
eh.AddBcc(resourceOwner.Email);
eh.SetSubject("Booking Requested");
eh.SetBody(message.ToString());
eh.sendMessage();
return RedirectToAction("Index");
}
else
{
return View();
}
}
Now for the view items - the main view:
#model AssetManager.Models.CreateEventViewModel
#{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend id="bookingLegend">Place Booking</legend>
<div class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Event.Title)
#Html.ValidationMessageFor(model => model.Event.Title)
</div>
</div>
<div class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.StartDateTime)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Event.StartDateTime, new { #class = "date" })
#Html.ValidationMessageFor(model => model.Event.StartDateTime)
</div>
</div>
<div class="controlcontainer">
<div class="editor-label timeSelector">
#Html.LabelFor(model => model.Event.StartTime)
</div>
<div class="editor-field timeSelector">
#Html.EditorFor(model => model.Event.StartTime)
#Html.ValidationMessageFor(model => model.Event.StartTime)
</div>
</div>
<div class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.EndDateTime)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Event.EndDateTime, new { #class = "date" })
#Html.ValidationMessageFor(model => model.Event.EndDateTime)
</div>
</div>
<div class="controlcontainer">
<div class="editor-label timeSelector">
#Html.LabelFor(model => model.Event.EndTime)
</div>
<div class="editor-field timeSelector">
#Html.EditorFor(model => model.Event.EndTime)
#Html.ValidationMessageFor(model => model.Event.EndTime)
</div>
</div>
<div class="controlcontainer">
<div class="editor-label">
#Html.Label("Select Resource Type")
</div>
<div class="editor-field">
#Html.DropDownList("ResourceTypes", new SelectList(Model.ResourceTypes, "ResourceTypeID", "Title"), "-- Select Resource Type --", new { #id = "ddlResourceTypes" })
</div>
</div>
<div class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.ResourceID)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Event.ResourceID, new SelectList(Enumerable.Empty<SelectListItem>(), "ResourceType", "Name"), "-- Select Resource --", new { #id = "ddlResources" })
#Html.ValidationMessageFor(model => model.Event.ResourceID)
</div>
</div>
<div class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.DoesRecur)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Event.DoesRecur)
#Html.ValidationMessageFor(model => model.Event.DoesRecur)
</div>
</div>
<div id="recurType" class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.Pattern)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Event.Pattern, new SelectList(Model.Patterns, "PatternCode", "Pattern"), "-- Select Recurrence Pattern --")
#Html.ValidationMessageFor(model => model.Event.Pattern)
</div>
</div>
<div id="recurDayHappens" class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.DayIndex)
</div>
<div class="editor-field">
#Html.DropDownList("dayOfWeek")
#Html.ValidationMessageFor(model => model.Event.DayIndex)
</div>
</div>
<div id="recurInstance" class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.DayCount)
</div>
<div class="editor-field">
#Html.DropDownList("dayInstance")
#Html.ValidationMessageFor(model => model.Event.DayCount)
</div>
</div>
<div class="controlcontainer">
<p>
<input class="subButton" type="submit" value="Create" />
<input id="cancelBtn" class="cancelButton" type="button" value="Cancel" onclick="location.href='#Url.Action("Index", "Calendar")'" />
</p>
</div>
</fieldset>
}
Then there is an editor template for the TimeSpan items:
#model TimeSpan
#Html.DropDownList("Hours", Enumerable.Range(0, 24)
.Select(i => new SelectListItem { Value = i.ToString(),
Text = i.ToString(), Selected = Model.Hours == i })) :
#Html.DropDownList("Minutes", Enumerable.Range(0, 60)
.Select(i => new SelectListItem { Value = i.ToString(),
Text = i.ToString(), Selected = Model.Minutes == i }))
And finally a TimeBinder class:
public class TimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Ensure there's incomming data
var key_hours = bindingContext.ModelName + ".Hours";
var valueProviderResult_hours = bindingContext.ValueProvider
.GetValue(key_hours);
var key_minutes = bindingContext.ModelName + ".Minutes";
var valueProviderResult_minutes = bindingContext.ValueProvider
.GetValue(key_minutes);
if (valueProviderResult_hours == null || string.IsNullOrEmpty(valueProviderResult_hours.AttemptedValue)
|| valueProviderResult_minutes == null || string.IsNullOrEmpty(valueProviderResult_minutes.AttemptedValue))
{
return null;
}
// Preserve it in case we need to redisplay the form
bindingContext.ModelState.SetModelValue(key_hours, valueProviderResult_hours);
bindingContext.ModelState.SetModelValue(key_minutes, valueProviderResult_minutes);
// Parse
var hours = ((string[])valueProviderResult_hours.RawValue)[0];
var minutes = ((string[])valueProviderResult_minutes.RawValue)[0];
// A TimeSpan represents the time elapsed since midnight
var time = new TimeSpan(Convert.ToInt32(hours), Convert.ToInt32(minutes), 0);
return time;
}
}
That's it, that is all the code that is involved. I am completely baffled as to why this error occurs. Any ideas or suggestions as to the cause and the solution are greatly appreciated.
Many thanks
nathj07
EDIT
Pk, so I tried something differnt with th TimeSpan editor template:
#model TimeSpan?
#Html.DropDownList("Hours", Enumerable.Range(0, 24)
.Select(i => new SelectListItem
{
Value = i.ToString(),
Text = i.ToString(),
Selected = Model.HasValue ? Model.Value.Hours == i : false
})) :
#Html.DropDownList("Minutes", Enumerable.Range(0, 60)
.Select(i => new SelectListItem
{
Value = i.ToString(),
Text = i.ToString(),
Selected = Model.HasValue ? Model.Value.Minutes == i : false
}))
This seems to have overcome this error but now I get an issue a little further down. In the view there is a DropDownList("ResourceTypes"....) This is essentially a dropdownlist that is used to control what appears in the DropDownListFor(model=>model.Event.ResourceID.....) There is a simple piece of JavaScript:
$(document).ready(function () {
$("#ddlResourceTypes").change(function () {
var idResourceType = $('#ddlResourceTypes').val();
$.getJSON("/Resource/LoadResourcesByType", { id: idResourceType },
function (resourceData) {
var select = $("#ddlResources");
select.empty();
select.append($('<option/>', {
value: 0,
text: "-- Select Resource --"
}));
$.each(resourceData, function (index, itemData) {
select.append($('<option/>', {
value: itemData.Value,
text: itemData.Text
}));
});
});
});
});
Now the issue I get is:
Object reference not set to an instance of an object
On the DropDownList("ResourceTypes".....)
Any ideas on this one?
When you POST an invalid form, you end with the code return View().
So you display the same view, without passing a model, the Model will be null. The first time your code really needs a value is in the editor for TimeSpan. That value is a nullable now, but you never test for the case that it is null.
Change the return to:
return View(em);
to pass the model, or use the code from the GET action, to rebuild and pass the model:
return Create(/* Your create date */);
Edit after comment
The error in the ModelBinder might be caused by the lines:
var hours = ((string[])valueProviderResult_hours.RawValue)[0];
var minutes = ((string[])valueProviderResult_minutes.RawValue)[0];
You convert an array to a string[]. I would do the conversion to string as late as possible, and make it more error proof by:
var hours = Convert.ToString(((object[])valueProviderResult_hours.RawValue).FirstOrDefault()) ?? "00";
This will just cast to an array of object, so there's less change to fail. Take the first element, or return null, and convert that to a string using Convert, and if the result is still null, return "00".
Hit breakpoints in your partial views and check the Model object, somewhere you will find a wrong object for a partial view. that is the cause of this error

Resources