I am working in an mvc problem and I found my self stuck in a problem trying to bind datetimepicker data to my controller action. I have a view that add a partial to collection of partial views when the user do some action.
My Models looks like
public class CreateOCVM
{
private string errors { get; set; }
public operaciones_confidenciales oc { get; set; }
[Display(Name = "Restringidas")]
public ICollection<PersonaMin> restringidas { get; set; }
public class PersonaMin
{
public int Id { get; set; }
public string motivo { get; set; }
[DataType(DataType.Date)]
public DateTime fecha_acceso;
In my view I have
#using (Html.BeginForm("CreateOC", "OC", FormMethod.Post, new { id = "form" }))
<table id="personasRestringidas" class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>id</th>
<th>Motivo</th>
<th>Fecha de acceso</th>
<th>Eliminar</th>
</tr>
</thead>
<tbody id="person">
#foreach (PersonaMin item in Model.restringidas)
{
#Html.Partial("~/Views/OC/AddOCRestriccion.cshtml", item)
}
</tbody>
</table>
When user do some ation I add a new item to the ICOllection restringidas. By making an ajax call to a controller method as follows:
function addRestriccion(idPersona) {
if (jQuery.inArray(idPersona, dataset) != 0) {
$.get('/OC/AddOCRestriccion', { id: idPersona}, function (template) {
dataset.push(idPersona);
$("#person").append(template);
$('#datetimepicker' + idPersona).datetimepicker();
$('#datetimepicker' + idPersona).datetimepicker({ format: "DD/MM/YYYY hh:mm" });
$("#datetimepicker" + idPersona).on("change.dp", function (e) {
$('#fechaAcceso' + idPersona).val($("#datetimepicker" + idPersona).datepicker('getFormattedDate'))
});
});
}
This call a controller method an returns a partial view that will be added to the main view.
[HttpGet]
public ActionResult AddOCRestriccion(Int32 id)
{
PersonaMin item = new PersonaMin();
item.Id = id;
persona p = PersonaCollection.getPersonasById(id);
return PartialView("AddOCRestriccion", item);
}
Edit:
This controller returns the partial view to add in the main one.
<tr>
#using (Html.BeginCollectionItem("restringidas"))
{
<td>
#Html.HiddenFor(model => model.Id)
#Html.DisplayFor(model => model.Id, new { #placeholder = "id", #id = "ID" })
</td>
<td>
#Html.TextAreaFor(model => model.motivo, new { #placeholder = "motivo", #id = "motivo", #class = "form-control" })
</td>
<td>
<div class='input-group date' id="#("datetimepicker" + Model.Id)">
<input type='text' class="form-control" />
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
#Html.HiddenFor(model => model.fecha_acceso, new { #id = "fechaAcceso" + Model.Id })
</div>
</td>
<td class="text-center">
<span class="input-group-btn">
<button type="button" class="btn btn-primary" onclick="deleteAnother($(this).parent().parent())">
<i class="fa fa-minus"></i>
</button>
</span>
</td>
}
When I submit the form it is handled by the controller action
[HttpPost]
public ActionResult CreateOC(CreateOCVM ocvm)
{ ... do something ....
The problem is that when i check the values on my model all the values are ok but not the one for the datetime (fecha_acceso)
In my view the hidden field for my datetime property has value on it.
But the value change when i check it on the controller method.
I tried with diferent aproaches but i always get wrong data on my controller method. Does somebody knows what is happening? Or has somebody a different aproach that can help me to do the same?
Thanks.
By default controller doesn't allow date format as dd-mm-yyyy.
Add below lines in your web.config file to do it
<configuration>
<system.web>
<globalization culture="en-GB" />
</system.web>
</configuration>
Looks like my Html.BeginCollectionItem helper has problems with DateTime so y change de type of my viewmodel from datetime to string. I know it is a workaround and not the correct solution but the lack of time force me to do it.
Related
I need my user to enter the seconds as an input but right after the user enter it I want to convert seconds to minutes and show it to user in label. What am I missing here ?
View
#Html.BSMaterialTextBoxFor(model => model.seconds, "Enter the seconds",colCSS: "col-6", htmlAttributes: new { data_bindrop = "seconds" , autocomplete = "off" })
#Html.LabelFor(model => model.secondsStr)
ViewModel
public int seconds{ get; set; }
[Display(Name = "Time")]
public string secondsStr
{
get
{
return (seconds/ 60).ToString() +" Minutes"+ " " + (seconds% 60).ToString()+"Seconds";
}
}
Next to time , it should write 0 minutes and 45 seconds
There are even easier way to handle what you are trying to achieve.
Please follow the steps below:
Model:
public class SecondModel
{
public double Seconds { get; set; }
}
Controller For Display:
public IActionResult Index()
{
ViewBag.Minutes = TempData["Minutes"];
return View();
}
Controller When Submit:
[HttpPost]
public async Task<IActionResult> AddSecond(SecondModel second)
{
TimeSpan t = TimeSpan.FromSeconds(second.Seconds);
string convertedMinutes = string.Format("{0:D2}m:{1:D2}s", t.Minutes, t.Seconds);
TempData["Minutes"] = convertedMinutes;
return RedirectToAction("Index");
}
Note: Point to remember in C# or asp.net Core, there is a built in method to do that. I am just using TimeSpan.FromSeconds to
implement that.
View:
#model DotNet6MVCWebApp.Models.SecondModel
<div class="row">
<div class="form-group">
<form class="form-group" method="post" asp-controller="Second" asp-action="AddSecond">
<table style="border-spacing: 12px;">
<tr>
<td><strong>Enter Seconds</strong></td>
<td><input asp-for="Seconds" class="form-control" /></td>
<td><button type="submit" class="btn btn-primary">Convert Second In Minutes</button></td>
<td>You have Entered-<strong>#ViewBag.Minutes</strong></td>
</tr>
</table>
</form>
</div>
</div>
Output:
I am encountering the following error:
A data source must be bound before this operation can be performed.
I have a text box, the user enters a name, and clicks the submit button. The name is added to a list. I have researched the error but everything I try gives me the same error. Any insight on what I am doing wrong will be appreciated.
Model:
namespace RangeTest.Models
{
public class UserNameModel
{
[Key]
public int Id { get; set; }
[Display(Name = "Names Added List")]
public string FullName { get; set; }
}
}
Controller:
public ActionResult Admin()
{
return View();
}
[HttpPost]
public ActionResult Admin(UserNameModel model, IEnumerable<RangeTest.Models.UserNameModel> t)
{
List<UserNameModel> userList = new List<UserNameModel>();
model.FullName = t.FirstOrDefault().FullName;
userList.Add(model);
return View(userList.ToList());
}
View (Admin.cshtml):
#model IEnumerable<RangeTest.Models.UserNameModel>
#{
ViewBag.Title = "";
}
<div class="container"></div>
<div class="jumbotron">
#using (Html.BeginForm("Admin", "UserNames", FormMethod.Post, new { #id = "WebGridForm" }))
{
#Html.ValidationSummary(true)
WebGrid dataGrid = new WebGrid(Model, canPage: false, canSort: false);
//Func<bool, MvcHtmlString> func =
//(b) => b ? MvcHtmlString.Create("checked=\"checked\"") : MvcHtmlString.Empty;
<div class="table-bordered">
<div class="Title">Admin - Add names to range list</div><br />
<table id="TblAdd"class="table">
<tr>
#{
RangeTest.Models.UserNameModel t = new RangeTest.Models.UserNameModel();
}
#Html.Partial("_AddDynTable", t)
</table>
</div>
<div class="table-responsive">
<div class="Title">Names Added to Range List</div>
<table class="table">
<tr>
<td >
#dataGrid.GetHtml(columns: dataGrid.Columns(dataGrid.Column(format: #<text>#item</text>),
dataGrid.Column("", format: (item) => Html.ActionLink("Delete", "Delete", new { id = item.id }))))
</td>
</tr>
</table>
</div>
}
</div>
Partial View (_addDynTable.cshtml)
#model RangeTest.Models.UserNameModel
<tr>
<td>
Enter Full Name: #Html.TextBoxFor(m => m.FullName)
#*<input type="button" value="Create" id="ClickToAdd" />*#<input class="CreateBtn" type="submit" value="Add to List" /></td>
</tr>
Can you link info about the error please?
One thing I can see is your t object is null when you pass it to the partial view
RangeTest.Models.UserNameModel t = new RangeTest.Models.UserNameModel();
A new UserNameModel with no data in it, if your error is there that might be the problem
When I submit, my model is empty on post.
Model
public QuizModel()
{
Questions = new List<QuizQuestionModel>();
}
public QuizModel(string quizName)
{
QuizName = quizName;
Score = 0;
IntranetEntities db = new IntranetEntities();
Quiz quiz = db.Quizs.Where(x => x.Name.Equals(quizName, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
if (quiz != null)
{
IQueryable<Quiz_Question> questions = db.Quiz_Question.Where(x => x.QuizID.Equals(quiz.ID)).OrderBy(x => x.QuestionNo);
Questions = new List<QuizQuestionModel>();
foreach (Quiz_Question question in questions)
{
QuizQuestionModel q = new QuizQuestionModel();
q.ID = question.ID;
q.Question = question.Question;
q.UserAnswer = null;
q.SystemAnswer = question.Answer;
Questions.Add(q);
}
}
}
public string QuizName { get; set; }
public List<QuizQuestionModel> Questions { get; set; }
public int Score { get; set; }
Controller
[HttpPost]
public ActionResult OSHAQuiz(Models.QuizModel model)
{
if (ModelState.IsValid)
{
bool passed = false;
model.Score = model.Questions.Where(x => x.UserAnswer.Equals(x.SystemAnswer, StringComparison.InvariantCultureIgnoreCase)).Count();
if (!model.Score.Equals(0))
{
double percent = model.Score / model.Questions.Count();
if (percent >= .8)
{
passed = true;
}
}
if (passed)
{
return View("/Views/Quiz/Passed.cshtml");
}
else
{
return View("/Views/Quiz/Failed.cshtml");
}
}
else
{
return View("/Views/Quiz/Quiz.cshtml", model);
}
}
View
#model PAL.Intranet.Models.QuizModel
<script>
$(document).ready(function () {
$("input:checked").removeAttr("checked");
});
</script>
<div class="grid">
<h2>OSHA Quiz</h2>
<hr />
<div class="align-center">
#using (Html.BeginForm("OSHAQuiz", "Quiz", FormMethod.Post, new { id = "formShowLoading" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="row cell">
<div class="example bg-grayLighter" data-text="Directions">
<ul class="simple-list">
<li class="align-left">When you have made your selection for all 20 statements, click on the button 'Submit.'</li>
<li class="align-left">Mark 'True' or 'False' for each statement.</li>
<li class="align-left">You must score 80% (16 correct) to pass.</li>
<li class="align-left">You must fill in your full name to receive credit.</li>
</ul>
</div>
</div>
<div class="row cell">
<div class="row cell">
<div class="panel" data-role="panel">
<div class="heading">
<span class="title">Questions</span>
</div>
<div class="content">
<ul class="numeric-list">
#foreach (var question in Model.Questions)
{
<li>
<table class="table hovered" style="width: 100%;">
<tr>
<td align="left">#question</td>
<td align="right" width="150px">
<div class="align-center">
<label class="align-right input-control radio small-check">
#Html.RadioButtonFor(model => question.UserAnswer, true, new { Name = question.GroupName })
<span class="check"></span>
<span class="caption">True</span>
</label>
<label class="align-right input-control radio small-check">
#Html.RadioButtonFor(model => question.UserAnswer, false, new { Name = question.GroupName })
<span class="check"></span>
<span class="caption">False</span>
</label>
</div>
</td>
</tr>
</table>
</li>
}
</ul>
</div>
</div>
</div>
</div>
<div class="row cell">
<input type="submit" value="Submit" class="button info small-button" />
<input type="reset" value="Reset" class="button primary small-button" />
</div>
}
</div>
</div>
UPDATE
if I use HiddenFor on QuizName, it does come back over on post but the rest of the model is empty.
When iterating over a collection that you want to post back to your model, you can't use foreach; you must use a regular for statement with indexing in order for Razor to generate the correct field names.
#for (var i = 0; i < Model.Questions.Count(); i++)
{
...
#Html.RadioButtonFor(m => m.Questions[i].UserAnswer)
}
Then, your fields will have name attributes in the form of Questions[0].UserAnswer, which the modelbinder will recognize and bind appropriately to your model. As you have it now, with the foreach, the field name is being generated as question.UserAnswer, which the modelbinder has no idea what to do with and discards.
Also, FWIW, accessing your context from within your model entity is a hugely bad idea, and even worse if you're not injecting it. Move that logic out of your entity and utilize a utility class or service instead. Also, look into dependency injection, as your context is one of those things that you want one and only one instance of per request. If you start instantiating multiple instances of the same context, you will have problems.
The problem is model binding is going to attempt to bind your form values to properties on your model. It will not use the constructor on your model that takes the quiz name, it will use the default constructor to instantiate the QuizModel object.
I would consider refactoring this model to remove your EntityFramework dependency and find a new way to populate those values.
You should also call the Dispose() method on IDisposable objects when you're done using them.
My suggestion for how to solve this problem would be to use the QuizModel you currently have to help render your view (i.e. Your quiz questions and possible answer for each question).
Create a seperate ViewModel for quiz submission
public class QuizSubmission
{
public string QuizName { get;set; }
public List<QuizQuestionResponse> Responses { get;set; }
}
public class QuizQuestionResponse
{
public int QuestionId { get;set; }
public int AnswerId { get;set; }
}
In your controller action, you should be binding to a QuizSubmission model.
[HttpPost]
public ActionResult OSHAQuiz(Models.QuizSubmission model)
{
Then you'll be able to perform any actions you need for that quiz submission (ie. data access, validation ).
You'll also need to update your view so that your Html input elements have the correct name attributes that model binding can correctly bind each question and response pair to a QuizQuestionResponse item in your Responses list.
I am having difficulty passing an IEnumerable as a model. The data is populating a form on one page - and doing so correctly. Upon submission the model returns as null.
I've seen various posts on this and they mostly reference naming-conventions so I have attempted different methods of naming the parameters to try to avoid any confusion in the model binding.
I have also tried various models and helpers to try and pass the data and all have the same result.
Current implementation:
Models:
public class UserProfileListModel
{
public IEnumerable<UserProfileViewModel> UserProfileViewModels { get; set; }
}
public class UserProfileViewModel
{
public UserProfile UserProfile { get; set; }
public Role UserRole { get; set; }
public Team UserTeam { get; set; }
public Scope UserScope { get; set; }
}
View:
#model Project.WebUI.Models.UserPRofileListModel
SNIP
<fieldset>
<legend>Administrate Users:</legend>
<table class="adminTbl">
<thead>
<tr>
<th>UserName:</th>
<th>Role:</th>
<th>Team:</th>
<th>Scope:</th>
<th>Update:</th>
<th>Delete:</th>
</tr>
</thead>
<tbody>
#{foreach (var user in Model.UserProfileViewModels)
{
<tr>
<td>
<p>#user.UserProfile.UserName
#{if (!user.UserProfile.Membership.IsConfirmed)
{
using (Html.BeginForm("Confirm", "Account", FormMethod.Post, null)){
#Html.AntiForgeryToken()
#Html.Hidden("Token", user.UserProfile.Membership.ConfirmationToken)
#Html.Hidden("Name", user.UserProfile.UserName)
}
<input type="submit" value="Confirm" />}
}
</p>
</td>
#{using (Html.BeginForm("SaveUserChanges", "Account", FormMethod.Post, null))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(u => user.UserProfile)
if (user.UserProfile.UserName != User.Identity.Name && user.UserProfile.Membership.IsConfirmed)
{
<td>
#Html.DropDownListFor(u => user.UserRole, Project.WebUI.Controllers.AccountController.RoleList, new { #class = "formdrop" })
</td>
<td>
#Html.DropDownListFor(u => user.UserTeam, Project.WebUI.Controllers.AccountController.TeamList, new { #class = "formdrop" })
</td>
<td>
#Html.DropDownListFor(u => user.UserScope, Project.WebUI.Controllers.AccountController.ScopeList, new { #class = "formdrop" })
</td>
<td>
<input type="submit" value="Save Changes" onclick="return confirm('Are you sure you wish to update this user? ')" />
</td>
}
else
{
/*If user is self or not yet confirmed these are here to buffer the delete button into the last cell*/
<td></td>
<td></td>
<td></td>
<td></td>
}
}
}
<td>
#Html.ActionLink("Delete", "Delete", new { user.UserProfile.UserId }, new
{
onclick = "return confirm('Warning: Action cannot be undone. Are you sure you wish to permanently delete this entry?')"
})
</td>
</tr>
}
}
</tbody>
</table>
</fieldset>
Controller:
Populate View:
public ActionResult AdministrateUsers()
{
populateLists();
var query = repository.UserProfiles.OrderBy(e => e.UserName);
List<UserProfileViewModel> list = new List<UserProfileViewModel>();
foreach(UserProfile up in query)
{
UserProfileViewModel vm = new UserProfileViewModel() { UserProfile = up };
list.Add(vm);
}
UserProfileListModel models = new UserProfileListModel()
{
UserProfileViewModels = list.OrderBy(up => up.UserProfile.UserName)
};
return View(models);
}
Accept Post:
public ActionResult SaveUserChanges(UserProfileListModel model)
{
foreach (UserProfileViewModel upvm in model.UserProfileViewModels)
{
UserProfile up = new UserProfile()
{
UserId = upvm.UserProfile.UserId,
UserEmail = upvm.UserProfile.UserName,
UserName = upvm.UserProfile.UserName
};
if (ModelState.IsValid)
{
repository.SaveUserProfile(up);
}
else
{
return View(model);
}
}
return RedirectToAction("Index", "Admin");
}
The code does still need a lot of work but I can't get past getting the model back to the controller on post. I have also tried returning the UserProfileViewModel instead of the entire list.
Can anyone tell what I am doing wrong?
Thanks!
You have a lot of invalid html including form elements as child elements of tr elements and duplicate id attributes. If you want to post back UserProfileListModel then you need a single form element and use an EditorTemplate or a for loop (not foreach) to render the controls so they are correctly named with indexers.
You are also trying to bind your dropdown lists to complex objects (for example UserProfile, Role etc.). <select> elements (and all form controls) only post back key/value pairs so you need to bind to a value type (for example UserProfile.UserId).
Your SaveUserChanges() post method is also trying access properties of UserProfile but you don't even have controls for properties of UserProfile in the form that post back to this method (for example UserId = upvm.UserProfile.UserId, UserEmail = upvm.UserProfile.UserName, ...) so they will always be null.
You probalby need to bind properties in POST method like here:
public ActionResult Create([Bind(Include = "Id,Subject,Text,IsImportant")] Announcment announcment) {... }
So it will be:
public ActionResult SaveUserChanges([Bind(Include = "UserProfile,Role,UserTeam,UserScope")]UserProfileListModel model)
Have you specified your action method is for HTTP Post? And change your action method to accept UserProfileViewModels instead.
[HttpPost]
public ActionResult SaveUserChanges(UserProfileViewModels model)
{
You are also only posting back one model: UserProfileViewModels.
You have your form in your foreach loop, so each UserProfileViewModels has its own form. If you want to change it to post back your UserProfileListModel, move
#{using (Html.BeginForm("SaveUserChanges", "Account", FormMethod.Post, null))
outside of your foreach.
How do I get the drop down to display as part of my editor template?
So I have a Users entity and a Roles entity. The Roles are passed to the view as a SelectList and User as, well, a User. The SelectList becomes a drop down with the correct ID selected and everything thanks to this sample.
I'm trying to get an all-in-one nicely bundled EditorTemplate for my entities using MVC 3 so that I can just call EditorForModel and get the fields laid out nicely with a drop down added whenever I have a foreign key for things like Roles, in this particular instance.
My EditorTemlates\User.cshtml (dynamically generating the layout based on ViewData):
<table style="width: 100%;">
#{
int i = 0;
int numOfColumns = 3;
foreach (var prop in ViewData.ModelMetadata.Properties
.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml)
{
#Html.Display(prop.PropertyName)
}
else
{
if (i % numOfColumns == 0)
{
#Html.Raw("<tr>");
}
<td class="editor-label">
#Html.Label(prop.PropertyName)
</td>
<td class="editor-field">
#Html.Editor(prop.PropertyName)
<span class="error">#Html.ValidationMessage(prop.PropertyName,"*")</span>
</td>
if (i % numOfColumns == numOfColumns - 1)
{
#Html.Raw("</tr>");
}
i++;
}
}
}
</table>
On the View I'm then binding the SelectList seperately, and I want to do it as part of the template.
My Model:
public class SecurityEditModel
{
[ScaffoldColumn(false)]
public SelectList roleList { get; set; }
public User currentUser { get; set; }
}
My Controller:
public ViewResult Edit(int id)
{
User user = repository.Users.FirstOrDefault(c => c.ID == id);
var viewModel = new SecurityEditModel
{
currentUser = user,
roleList = new SelectList(repository.Roles.Where(r => r.Enabled == true).ToList(), "ID", "RoleName")
};
return View(viewModel);
}
My View:
#model Nina.WebUI.Models.SecurityEditModel
#{
ViewBag.Title = "Edit";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Edit</h2>
#using(Html.BeginForm("Edit", "Security"))
{
#Html.EditorFor(m => m.currentUser)
<table style="width: 100%;">
<tr>
<td class="editor-label">
User Role:
</td>
<td class="editor-field">
<!-- I want to move this to the EditorTemplate -->
#Html.DropDownListFor(model => model.currentUser.RoleID, Model.roleList)
</td>
</tr>
</table>
<div class="editor-row">
<div class="editor-label">
</div>
<div class="editor-field">
</div>
</div>
<div class="editor-row"> </div>
<div style="text-align: center;">
<input type="submit" value="Save"/>
<input type="button" value="Cancel" onclick="location.href='#Url.Action("List", "Clients")'"/>
</div>
}
Hopefully that's clear enough, let me know if you could use more clarification. Thanks in advance!
Since you need access to the SelectList you can either create an editor template bound to SecurityEditModel or you can pass the SelectList in ViewData. Personally I would go with the strongly typed approach.