I am new to MVC and I am trying to build a small test application to get a clear idea about how the SimpleMembershipProvider works in MVC 4. I have created two Roles - Student and Teacher. I have only one user with Teacher Role assigned(hard coded in Configuration.cs), who is going to create Students and while creating a new student, the Teacher will also generate the UserID and password for that student.The newly created student will then log on to the application with the userid and password as created by the Teacher and will do certain tasks.
The problem is whenever the user with Teacher Role creates a student, the current logged in user with Teacher Role gets logged out and the newly created student gets logged in. What I have done is, I have not kept any field for userid and password in Student Model. I have used a partial view bound to RegisterModel(from AccountModels) to generate the fields for Username and password while creating new Student. I have kept StudentID in UserProfile Model.
This is my code as shown below for further clarity.
Student Model
public class Student
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[DisplayName("First Name")]
public string FirstName { get; set; }
[DisplayName("Last Name")]
public string LastName { get; set; }
[DisplayName("Date of Birth")]
public string DateOfBirth { get; set; }
public Gender Gender { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
UserProfile Model
public class UserProfile
{
public int UserId { get; set; }
public string UserName { get; set; }
public int StudentId { get; set; }
}
This is from Configuration.cs where I am creating the Roles and a user with Teacher Role
private void SeedMemebership()
{
WebSecurity.InitializeDatabaseConnection("DefaultConnection1",
"UserProfile", "UserId", "UserName", autoCreateTables: true);
var roles = (SimpleRoleProvider)Roles.Provider;
var membership = (SimpleMembershipProvider)Membership.Provider;
if (!roles.RoleExists("Teacher"))
{
roles.CreateRole("Teacher");
}
if (!roles.RoleExists("Student"))
{
roles.CreateRole("Student");
}
if (membership.GetUser("UserFoo", false) == null)
{
membership.CreateUserAndAccount("UserFoo", "Password");
}
if (!roles.GetRolesForUser("UserFoo").Contains("Teacher"))
{
roles.AddUsersToRoles(new[] { "UserFoo" }, new[] { "Teacher" });
}
}
Controller Actions To Create Student-
//
// GET: /Student/Create
[Authorize(Roles = "Teacher", Users = "UserFoo")]
public ActionResult Create()
{
return View();
}
//
// POST: /Student/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Student student, RegisterModel model)
{
if (ModelState.IsValid)
{
try
{
db.Students.Add(student);
db.SaveChanges();
WebSecurity.CreateUserAndAccount(model.UserName, model.Password, new { StudentId = student.Id });
WebSecurity.Login(model.UserName, model.Password);
return RedirectToAction("Index", "Student");
}
catch (MembershipCreateUserException e)
{
ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
}
}
return View(student);
}
Corresponding Views -
#model TestApp.Models.Student
#{
ViewBag.Title = "Create";
}
<script type="text/javascript" src="~/Scripts/MyCustom.js"></script>
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Student</legend>
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DateOfBirth)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DateOfBirth)
#Html.ValidationMessageFor(model => model.DateOfBirth)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Gender)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Gender, new SelectList(Enum.GetValues(typeof(TestApp.Models.Gender))))
#Html.ValidationMessageFor(model => model.Gender)
</div>
<div class="float-right-top">
#Html.Partial("_PartialRegisterStudent")
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Partial View "_PartialRegisterStudent.cshtml
#model TestApp.Models.RegisterModel
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<fieldset>
<ol>
<li>
#Html.LabelFor(m => m.UserName)
#Html.TextBoxFor(m => m.UserName)
</li>
<li>
#Html.LabelFor(m => m.Password)
#Html.PasswordFor(m => m.Password)
</li>
<li>
#Html.LabelFor(m => m.ConfirmPassword)
#Html.PasswordFor(m => m.ConfirmPassword)
</li>
</ol>
</fieldset>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
What will be the best way to create students with Teacher Role without the logged in user logging out of the application? I thought about using inheritance(Parent - UserProfile, Child-Student) and one to one relationship between student model and userprofile model but could not make out how to make it work. Code Samples will be very much appreciated!
P.S. Parden me if this post is a bit lengthy. Thank you.
Just delete below line in Create Action, it should work.
WebSecurity.Login(model.UserName, model.Password);
Related
Im trying to use only 1 html.editorfor to fill in two model field in each of my model.
I want the value of this editorfor to be also inserted to my Clientes0013.Client0013
<div class="editor-field">
#Html.EditorFor(model => model.CanaClie0012.Client00130012)
#Html.ValidationMessageFor(model => model.CanaClie0012.Client00130012)
</div>
and this one to be also inserted to Clientes0013.F1Pais00200013
<div class="editor-field">
#Html.EditorFor(model => model.CanaClie0012.F1Pais00200012)
#Html.ValidationMessageFor(model => model.CanaClie0012.F1Pais00200012)
</div>
Kindly show me what should be the right way of doing this.
My Table Model:
public partial class CanaClie0012
{
public string Client00130012 { get; set; }
public string F1Pais00200012 { get; set; }
public string F1Cana02530012 { get; set; }
public string Direcc0012 { get; set; }
public Nullable<System.DateTime> TmStmp0012 { get; set; }
}
public partial class Clientes0013
{
public string Client0013 { get; set; }
public string Nombre0013 { get; set; }
public string F1Pais00200013 { get; set; }
}
My Custom Model to combine the two table is:
public class ClientModel
{
public CanaClie0012 CanaClie0012 { get; set; }
public Clientes0013 Clientes0013 { get; set; }
}
My Controller:
[HttpPost]
public ActionResult ClientCreate(CanaClie0012 canaclie0012, Clientes0013 clientes0013)
{
ClientModel vm = new ClientModel();
if (ModelState.IsValid)
{
db.CanaClie0012.Add(canaclie0012);
db.Clientes0013.Add(clientes0013);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vm);
}
My View:
#model MvcApplication1.Models.ClientModel
#{
ViewBag.Title = "ClientCreate";
}
<h2>ClientCreate</h2>
<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()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>ClientModel</legend>
<div class="editor-label">
Client Name
</div>
<div class="editor-field">
#Html.EditorFor(model => model.CanaClie0012.Client00130012)
#Html.ValidationMessageFor(model => model.CanaClie0012.Client00130012)
</div>
<div class="editor-label">
Pais
</div>
<div class="editor-field">
#Html.EditorFor(model => model.CanaClie0012.F1Pais00200012)
#Html.ValidationMessageFor(model => model.CanaClie0012.F1Pais00200012)
</div>
<div class="editor-label">
Address
</div>
<div class="editor-field">
#Html.EditorFor(model => model.CanaClie0012.Direcc0012)
#Html.ValidationMessageFor(model => model.CanaClie0012.Direcc0012)
</div>
<div class="editor-label">
Contact Number
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Clientes0013.Nombre0013)
#Html.ValidationMessageFor(model => model.Clientes0013.Nombre0013)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Got it.. modified my controller action to this one.
[HttpPost]
public ActionResult ClientCreate(CanaClie0012 canaclie0012, Clientes0013 clientes0013)
{
ClientModel vm = new ClientModel();
if (ModelState.IsValid)
{
clientes0013.Client0013 = canaclie0012.Client00130012;
clientes0013.F1Pais00200013 = canaclie0012.F1Pais00200012;
db.CanaClie0012.Add(canaclie0012);
db.Clientes0013.Add(clientes0013);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vm);
}
I'm trying to build a flashcard application to learn MVC4. A Set contains Cards, and a Card contains Sides (to facilitate one or many-sided cards).
I have the following viewModel for Card Creation:
public class CreateCardViewModel
{
[HiddenInput(DisplayValue = false)]
public int SetId { get; set; }
[Required]
public ICollection<Side> Sides { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime DateCreated { get; set; }
[Required]
public bool IsReady { get; set; }
}
And the following actions defined for Create:
[HttpGet]
public ActionResult Create(int setId)
{
var model = new CreateCardViewModel();
// attach card to current set
model.SetId = setId;
// create a new Side
var side = new Side() {Content = "Blank Side"};
// Add this to the model's Collection
model.Sides = new Collection<Side> { side };
return View(model);
}
[HttpPost]
public ActionResult Create(CreateCardViewModel viewModel)
{
if (ModelState.IsValid)
{
var set = _db.Sets.Single(s => s.SetId == viewModel.SetId);
var card = new Card {Sides = viewModel.Sides};
set.Cards.Add(card);
_db.Save();
}
return View(viewModel);
}
In the view, I'm trying to start by displaying the Side that I created in the Controller, and allowing the user to edit it. I get "InvalidOperationException: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions." when I try to run with the following markup in the view:
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>CreateCardViewModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.SetId)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.SetId)
#Html.ValidationMessageFor(model => model.SetId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DateCreated)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DateCreated)
#Html.ValidationMessageFor(model => model.DateCreated)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.IsReady)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.IsReady)
#Html.ValidationMessageFor(model => model.IsReady)
</div>
// OFFENDING CODE
#foreach (var side in Model.Sides)
{
#Html.EditorFor(model => model.Sides.ElementAt(side.SideId))
}
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
How can I let the user edit the existing Side when creating a card?
Instead of using ElementAt(), just use the normal [] index operator:
#Html.EditorFor(model => model.Sides[side.SideId])
I want to know, is there a way that I can pass an object(let's say a Message instance) from the validation in the model's fields and retrieve them from the ModelState( or by using some other thing) instance. Why I'm asking this is, I want to differentiate between the validation error message types so I can display only the messages I want in the view at a time. (Ex : required messages shown before the unique validation messages.)
I was trying to use a custom created Message object which I can then distinguish using its messageType field. But as the validation only returns string messages, can't think of a way.
Are you ok with reading the messages out of ModelState and determining type based on message content? That can be done if you set a custom message for all the validations.
Then you can evaluate each message looking for specific content and take action. Such as putting the word "Error" in the Required attribute and Info in the rest.
Here's a class you can use to test
Model
public class EmployeeViewModel {
public int ID { get; set; }
[Display(Name = "First Name")]
[Required(ErrorMessage = "Error")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
[Required(ErrorMessage = "Error")]
public string LastName { get; set; }
[Display(Name = "Username")]
public string Username { get; set; }
[Display(Name = "Email Address")]
public string EmailAddress { get; set; }
}
Controller
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using TestApp.Models;
namespace TestApp.Controllers {
public class HomeController : Controller {
public ActionResult Index() {
return RedirectToAction("Test");
}
public ActionResult Test() {
var model = new EmployeeViewModel();
return View(model);
}
[HttpPost]
public ActionResult Test(EmployeeViewModel model) {
// Force an error on this property - THIS should be the only real error that gets returned back to the view
ModelState.AddModelError("", "Error on First Name");
if(model.EmailAddress == null) // Add an INFO message
ModelState.AddModelError("", "Email Address Info");
if (model.Username == null) // Add another INFO message
ModelState.AddModelError("", "Username Info");
// Get the Real error off the ModelState
var errors = GetRealErrors(ModelState);
// clear out anything that the ModelState currently has in it's Errors collection
foreach (var modelValue in ModelState.Values) {
modelValue.Errors.Clear();
}
// Add the real errors back on to the ModelState
foreach (var realError in errors) {
ModelState.AddModelError("", realError.ErrorMessage);
}
return View(model);
}
private IEnumerable<ModelError> GetRealErrors(IEnumerable<KeyValuePair<string, ModelState>> modelStateDictionary) {
var errorMessages = new List<ModelError>() ;
foreach (var keyValuePair in modelStateDictionary.Where(keyValuePair => keyValuePair.Value.Errors.Count > 0)) {
errorMessages.AddRange(keyValuePair.Value.Errors.Where(error => !error.ErrorMessage.Contains("Info")));
}
return errorMessages;
}
}
}
private IEnumerable<ModelError> GetRealErrors(IEnumerable<KeyValuePair<string, ModelState>> modelStateDictionary) {
var errorMessages = new List<ModelError>() ;
foreach (var keyValuePair in modelStateDictionary.Where(keyValuePair => keyValuePair.Value.Errors.Count > 0)) {
errorMessages.AddRange(keyValuePair.Value.Errors.Where(error => !error.ErrorMessage.Contains("Info")));
}
return errorMessages;
}
View
#model TestApp.Models.EmployeeViewModel
<h2>Test</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>EmployeeViewModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Username)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Username)
#Html.ValidationMessageFor(model => model.Username)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.EmailAddress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.EmailAddress)
#Html.ValidationMessageFor(model => model.EmailAddress)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
I've just created a new controller, along with its CRUD forms, etc, with a database-first EF model/entity.
Its throwing a number of validation errors on save, but since the form has validators, I don't see why this would be so.
For reasons that are beyond me, I'm not getting any validation to happen at all. It just goes right in to the saveChanges() call, which promptly fails.
Here's the edit form:
#model StatementsApplication.DAL.StatementTask
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>StatementTask</legend>
<div class="editor-label">
#Html.LabelFor(model => model.sInitials)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.sInitials)
#Html.ValidationMessageFor(model => model.sInitials)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.dtCompleted)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.dtCompleted)
#Html.ValidationMessageFor(model => model.dtCompleted)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.sGroupLabel)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.sGroupLabel)
#Html.ValidationMessageFor(model => model.sGroupLabel)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.nGroupSequence)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.nGroupSequence)
#Html.ValidationMessageFor(model => model.nGroupSequence)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.sTaskType)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.sTaskType)
#Html.ValidationMessageFor(model => model.sTaskType)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.sTaskLabel)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.sTaskLabel)
#Html.ValidationMessageFor(model => model.sTaskLabel)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.nTaskSequence)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.nTaskSequence)
#Html.ValidationMessageFor(model => model.nTaskSequence)
</div>
#Html.HiddenFor(model => model.ID)
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
and here's the generated model:
namespace StatementsApplication.DAL
{
using System;
using System.Collections.Generic;
public partial class StatementTask
{
public int StmtBatchID { get; set; }
public string sInitials { get; set; }
public Nullable<System.DateTime> dtCompleted { get; set; }
public string sGroupLabel { get; set; }
public double nGroupSequence { get; set; }
public string sTaskType { get; set; }
public string sTaskLabel { get; set; }
public double nTaskSequence { get; set; }
public int ID { get; set; }
public virtual StatementBatch tblStmtBatch { get; set; }
}
}
and here's the controller bits...
//
// GET: /StatementTask/Edit/5
public ActionResult Edit(int id = 0)
{
StatementTask statementtask = db.StatementTasks.Find(id);
if (statementtask == null)
{
return HttpNotFound();
}
ViewBag.StmtBatchID = new SelectList(db.StatementBatches, "ID", "sStatus", statementtask.StmtBatchID);
return View(statementtask);
}
//
// POST: /StatementTask/Edit/5
[HttpPost]
public ActionResult Edit(StatementTask statementtask)
{
if (ModelState.IsValid)
{
try
{
db.Entry(statementtask).State = EntityState.Modified;
db.SaveChanges();
}
catch (Exception ex) {
throw ex;
}
return RedirectToAction("Index");
}
ViewBag.StmtBatchID = new SelectList(db.StatementBatches, "ID", "sStatus", statementtask.StmtBatchID);
return View(statementtask);
}
Its a matter of some confusion for me as to why sInitials is throwing 'required' validation errors, as well as why sGroupLabel is throwing length validation errors.
Thanks
a) your model has no data validation annotations. As such, MVC doesn't do any validation, because you're not telling it what to validate.
b) You don't mention what you are submitting. Are you just submitting an empty form?
it appears that Data Annotations will resolve this issue
[Required(AllowEmptyStrings = true)]
[DisplayFormat(ConvertEmptyStringToNull = false)]
public object Note { get; set; }
via http://fendy-huang.blogspot.com/2011/04/how-to-pass-empty-string-in-entity.html
I have a DateTime field in my form. For whatever reason, everytime I submit the form ModelState comes back as invalid and a message that my date time field (called PostDate) is required, even though in the view model I don't have the required attribute set.
Does anyone know why this would be happening as I'm going around in circles trying to figure it out.
Here is the view model
public class BlogViewModel
{
[Required]
public string Title { get; set; }
[Required]
public string Content { get; set; }
public bool Published { get; set; }
public DateTime PostDate { get; set; }
}
Here is the controller actions
public ActionResult Create()
{
return View();
}
//
// POST: /Admin/Blog/Create
[HttpPost]
[ValidateInput(false)]
public ActionResult Create(BlogViewModel model)
{
if(ModelState.IsValid)
{
model.Content = HtmlSanitizer.SanitizeHtml(model.Content);
Services.BlogService.CreateBlogPost(model.Title, model.Content, User.Identity.Name);
return RedirectToAction("Index");
}
return View(model);
}
Here is the View
#using Payntbrush.Infrastructure.Mvc.Extensions
#model Payntbrush.Presentation.Demo.MVC3.Areas.Admin.Models.BlogViewModel
#Html.Resource(Html.ScriptTag("Areas/Admin/js/plugins/wysiwyg/jquery.wysiwyg.js"), ResourceType.Js)
<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((string)ViewBag.Action, "Blog", FormMethod.Post, new { Id = "BlogEditor" }))
{
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Content)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.Content, new { #class = "wysiwyg blog-editor" })
#Html.ValidationMessageFor(model => model.Content)
</div>
<div class="editor-label">
Time of post (Only set this if you want to make a post appear to have been published at a different date.)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.PostDate, new{#class="datetimepicker"})
#Html.ValidationMessageFor(model => model.PostDate)
</div>
<div class="editor-label">
Published (Check to make this post live on the site)
</div>
<div class="editor-field">
#Html.CheckBoxFor(model => model.Published)
#Html.ValidationMessageFor(model => model.Published)
</div>
<p>
<input class="large awesome" type="submit" value="Create" />
#Html.ActionLink("Cancel", "Index", "Blog", null, new { #class = "large awesome cancel-button" })
</p>
}
<div>
</div>
If you leave the corresponding field empty your model will be invalid because DateTime is a value type. You should use a nullable DateTime if you want to allow empty values:
public DateTime? PostDate { get; set; }