I have a set up where a Company can have none or one or many clients. So there is no strict relationship between the Client table and the Company table. I have created a Search view where all companies are populated. Then using a button a client can be attached to the company. I thought using an ActionLink I would be able to achieve this, so my Search (view) has,
#Html.ActionLink("Book", "Book", new { id = a.CompanyId })
Where the Model is looped over to get all the company list. Now when I click the link, it populates the Address with the right params, Companies/Book/1 the ID I am playing with is 1. Which is correct, however the View I am landing at is a new Customer Model.
public class CustomerModel
{
[HiddenInput(DisplayValue = true)]
public long CompanyId { get; set; }
[HiddenInput(DisplayValue = false)]
public long CustomerId { get; set; }
[Display(Name = "Customer Name")]
[Required(ErrorMessage = "* required")]
public string CustomerName { get; set; }
[Display(Name = "Address Line 1")]
[Required(ErrorMessage = "* required")]
public string AddressLine1 { get; set; }
[Display(Name = "Postcode")]
[Required(ErrorMessage = "* required")]
public string Postcode { get; set; }
[Display(Name = "Phone Number")]
[Required(ErrorMessage = "* required")]
[RegularExpression(#"\d*", ErrorMessage = "Not a valid phone number")]
public string PhoneNo { get; set; }
}
Even though I am able to see the ID being passed (using FireBug) is 1, somehow when I click the button to submit the view to the controller I get a 0. Why would this be? Could anyone help me?
EDIT - 1
This is the controller.
public ActionResult Book()
{
return View(new CustomerModel());
}
[HttpPost]
public ActionResult SaveCustomer(CustomerModel model)
{
_companyService.SaveCustomer(model);
return RedirectToAction("Index");
}
I have tried using the CompanyId instead of id, it came up with another error.
Before submitting the Form, Address bar has : http://localhost:53294/Companies/Book?CompanyId=1
After submitting the Form, Address Bar has : http://localhost:53294/Companies/SaveCustomer
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Customer_dbo.Company_CompanyId". The conflict occurred in database "BoilerServicing", table "dbo.Company", column 'CompanyId'.
The statement has been terminated.
The save method by itself,
public void SaveCustomer(CustomerModel customer)
{
using (var db = new BoilerServicingDbContext())
{
Customer entity;
if (customer.CustomerId > 0)
{
entity = db.Customers.First(x => x.Id == customer.CustomerId);
}
else
{
entity = new Customer();
db.Customers.Add(entity);
}
entity.Name = customer.CustomerName;
entity.TelephoneNumber = customer.PhoneNo;
entity.AddressLine1 = customer.AddressLine1;
entity.PostCode = customer.Postcode;
entity.CompanyId = customer.CompanyId;
db.SaveChanges();
}
}
Okay, after trying so many ways, I have come to this. I changed the Action method on the controller.
public ActionResult Book(long id)
{
return View(new CustomerModel
{
CompanyId = id
});
}
This seems to have passed in the CompanyId I am passing into the Book view. Took me a while, but I got there. Thanks for all your help !
Related
I have an action method which outputs a model which has multiple sub models. In one of the sub model I have some additional properties which are not required in my view.
Sub model- ProjectModel-
[Required(ErrorMessage = "*")]
public int Id { get; set; }
[Required(ErrorMessage = "*")]
public int SectorDivisionId { get; set; }
[Required(ErrorMessage = "*")]
[StringLength(250, ErrorMessage = "Project name should not be more than 250 characters.")]
public string Program { get; set; }
[Required(ErrorMessage = "*")]
[StringLength(25, ErrorMessage = "Project number should not be more than 25 characters.")]
public string ProjectNumber { get; set; }
public string WorkPackage { get; set; }
public string WorkPackageType { get; set; }
[Required(ErrorMessage = "*")]
public DateTime StartDate { get; set; }
[Required(ErrorMessage = "*")]
public DateTime EndDate { get; set; }
public int ProjectDirectorId { get; set; }
So while initializing the sub model to my main model I am only using those properties which I need as shown below.
model.ProjectInfo = new ProjectModel()
{
Id = projectId,
ProjectNumber = prj.p.ProjectNumber,
Director = prj.Director,
Program = prj.p.Program,
StartDate = prj.p.StartDate,
EndDate = prj.p.EndDate,
ProjectReviewPeriodList = projectReviewPeriodList.AsEnumerable().
Select(o => new ProjectReviewPeriodModel
{
Id = o.Id,
ProjectReviewTypeId = o.ProjectReviewTypeId,
ProjectId = o.ProjectId,
ReviewPeriod = o.ReviewPeriod,
ReviewPeriodDate = o.ReviewPeriodDate
}).ToList()
};
Now, while posting the form I have an action filter at global level which validates the Model. The validation (ModelState.IsValid) fails for some of the fields from the sub model which I haven't initialized as per my needs.
I thought of two options-
Using ModelState.Remove(<PropertyName>) to skip validation. This is not possible as I am using a global level action filter.
Create a new view model
Is there any other way of doing this, preferably in the action method level?
Please let me know if any doubts or I can explain it more clearly.
Thanks.
The clean way would be to use different ViewModels for different usecases.
That being said, you can implement the validation logic with IValidatableObject instead of using Data Annotations attributes.
Introduce a flag into the ViewModel that indicates the usecase, e.g. IsEditUsecase. Set this flag somewhere where you know the usecase, e.g. in the controller.
Then only perform the validations that are needed for this usecase.
public class ProjectModel : IValidatableObject {
public bool IsEditUsecase { get; set; }
[Required(ErrorMessage = "*")] // required for every usecase
public int Id { get; set; }
// no [Required] and [StringLength] attributes
// because only required in some of the usecases
public string Program { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
// validation that occurs only in Edit usecase
if (IsEditUsecase) {
// Validate "Program" property
if (string.IsNullOrWhiteSpace(Program)) {
yield return new ValidationResult(
"Program is required",
new[] { "Program" }
);
}
else if (Program.Length > 250) {
yield return new ValidationResult(
"Project name should not be more than 250 characters.",
new[] { "Program" }
);
}
// validate other properties for Edit usecase ...
}
// validate other usecases ...
}
}
As a dirty hack, I have added hidden fields in my razor page for all those properties which caused ModelState validation error. Basically I added some default values for the hidden fields and it works fine now.
Not recommended though but it was a quick fix.
Error message:
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_UserProfile_UserLogin". The conflict occurred in database
"ToDoDB", table "dbo.UserLogin", column 'UserLoginID'. The statement
has been terminated.
What could this mean?
I am trying to build a simple log in and profile MVC5 web app. I created my table in SQL Express.
Firstly here is my model for the sign up page:
public class UserSignUp
{
[Key]
public int UserLoginID { get; set; }
//Foregin key for the login table - First name, last name, creation date, and email
public int UserProfileID { get; set; }
[Required(ErrorMessage = "Username is required")]
[Display(Name = "Username")]
public string Username { get; set; }
[Required(ErrorMessage = "Password is required")]
[Display(Name = "Password")]
public string Password { get; set; }
[Required(ErrorMessage = "First Name is required")]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name is required")]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[DataType(DataType.DateTime)]
public DateTime CreationDate { get; set; }
[Required(ErrorMessage = "Valid email is required")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
}
So the UserLoginID is the primary key from the UserLogin table and the UserProfileID is the primary from the UserProfile table. I set the foreign key of the UserProfile table to UserLoginID from the UserLogin.
Here is my model for creating a new user:
public class UserProfileManager
{
public void AddUserAccount(UserSignUp newUser)
{
// create database connection
using (ToDoDBEntities db = new ToDoDBEntities())
{
// Collect viewmodel data
// Here building goes by object type and not foregin key relationship
UserLogin UL = new UserLogin();
UL.Username = newUser.Username;
UL.Password = newUser.Password;
// Add the UserLogin object I just built to the database
db.UserLogins.Add(UL);
db.SaveChanges();
UserProfile UP = new UserProfile();
// establish connection to UL by establishing foreign key relationship
UP.UserLoginID = newUser.UserLoginID;
UP.FirstName = newUser.FirstName;
UP.LastName = newUser.LastName;
UP.CreationDate = newUser.CreationDate;
UP.Email = newUser.Email;
// Add UserProfile object to databse and save changes
db.UserProfiles.Add(UP);
db.SaveChanges();
}
}
//Check if user is real before login is allowed
public bool isLoginReal(string LoginName)
{
using (ToDoDBEntities DB = new ToDoDBEntities())
{
// Return the user from the DB whose login name matches the LoginName string passed in as perameter
return DB.UserLogins.Where(o => o.Username.Equals(LoginName)).Any();
}
}
}
My AddUserAccount is where I think I am having issues. So I start by building the UserLogin object and adding and saving to the database. That seems to work out actually. But the next step where I build, add, and save the UserProfile object doesn't seem to work. At least the database doesn't get updated.
Here is the controller handling the actions:
public class AccountController : Controller
{
// GET: Account
public ActionResult Index()
{
return View();
}
#region signup methods
// Get method for signup page
public ActionResult SignUpPage()
{
return View();
}
// Post method for signup page - post to db
[HttpPost]
// Pass in the UserSign up model object to be built
public ActionResult SignUpPage(UserSignUp USUV)
{
// Form is filled out and then method is entered
if (ModelState.IsValid)
{
// Form is filled out and database connection is established if form is valid
UserProfileManager UPM = new UserProfileManager();
if (!UPM.isLoginReal(USUV.Username))
{
// data access . adduseraccount from entity manager (where model objects are built)
UPM.AddUserAccount(USUV);
FormsAuthentication.SetAuthCookie(USUV.FirstName, false);
return RedirectToAction("Welcome", "Home");
}
else
{
}
}
return View();
}
#endregion
}
To my (noob) eye everything looks good. The SignUpPage is received, then a new UserSignUp object is passed into the Post action, and entity framework object (UserProfileManager) is built, the form is authenticated and the user gets either redirected to the Welcome view or the user gets returned to the signup view.
Any chance someone can help me figure out what I am either missing or doing wrong? I included a picture of the database design for reference (I know even less about database then MVC).
Ah yes, after the problem is here:
UP.UserLoginID = newUser.UserLoginID;
It's not newUser, it should be:
UP.UserLoginID = UL.UserLoginID;
Because you just added the object UL to the database, to get the generated ID of the inserted object, you have to call it, not the newUser object.
The objective that I'm trying to achieve is that you have a Team which has many Players (ApplicationUsers), and you have Players (ApplicationUsers) which can potentially have many Teams. (Currently I am just focused on assigning one team).
I am receiving the error (in the title of this question) upon creation of a new user (code is below). After successfully creating a new user the code will call:
CreateAsync(user, userViewModel.Password);
which is where it fails.
The error that I am receiving seems to be related to Entity Framework and the instructions it's sending to SQL (I have a local instance of SQL Express configured in the Connection String). I have searched StackOverflow for this error and there do seem to be a lot of posts, but I can't seem to find an answer that relates to this circumstance - they all seem to reference SQL directly. Unfortunately I am new to .NET and am weak in EF and SQL which is why I am reaching out.
I believe the issue is something to do with the foreign key that EF is trying to establish between Teams and ApplicationUsers given the "IdentitySample.Models.Team_Players" part of the error.
I am working off of the .NET Identity 2.0 sample project, and have added some additional properties to the included ApplicationUser class which relate to a new class I added, "Team".
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here - db NOTE: Registration of claims from "IdentityExtensions.cs" class. See file.
// These need to be added to the ViewModels and the Controllers to capture the data. See the "AccountViewModel.cs"
// file, the "ManageViewModels.cs" and the "AccountController.cs" files, plus the views for corresponding changes.
userIdentity.AddClaim(new Claim("firstName", firstName));
userIdentity.AddClaim(new Claim("lastName", lastName));
userIdentity.AddClaim(new Claim("number", number.ToString()));
return userIdentity;
}
[Display(Name = "First Name")]
public string firstName { get; set; }
[Display(Name = "Last Name")]
public string lastName { get; set; }
[Display(Name = "Hawks Number")]
public int number { get; set; }
[Display(Name = "Successful Logins")]
public int successfulLogins { get; set; }
[Display(Name = "Password Status")]
public bool tempPassword { get; set; }
public virtual ICollection<Team> Teams { get; set; }
public ApplicationUser()
{
Teams = new List<Team>();
}
}
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ApplicationUser> Players { get; set; }
public Team()
{
Players = new List<ApplicationUser>();
}
}
I have two DbContexts, the included ApplicationDbContext along with one I added - IdentityDB - which inherits from the ApplicationDbContext class. In the IdentityDB context I have added the Team DbSet, "Teams".
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
static ApplicationDbContext()
{
// Set the database intializer which is run once during application start
// This seeds the database with admin user credentials and admin role
Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
public class IdentityDB : ApplicationDbContext
{
public DbSet<Team> Teams { get; set; }
}
I am creating a user by creating an instance of a view model called RegistrationViewModel:
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[Display(Name = "First Name")]
public string firstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string lastName { get; set; }
[Display(Name = "Assigned Team")]
public string SelectedTeam { get; set; }
[Required]
[Display(Name = "Hawks Number")]
public int number { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[System.ComponentModel.DataAnnotations.Compare("Password", ErrorMessage= "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public ICollection<SelectListItem> Teams { get; set; }
public RegisterViewModel()
{
Teams = new List<SelectListItem>();
}
}
in the UserAdminController:
//
// GET: /Users/Create
public async Task<ActionResult> Create()
{
//Get the list of Roles
ViewBag.RoleId = new SelectList(await RoleManager.Roles.ToListAsync(), "Name", "Name");
//Get list of Teams
IdentityDB _db = new IdentityDB();
var teams = _db.Teams.Select(t => new SelectListItem
{
Value = t.Name,
Text = t.Name
}).ToList();
return View(new RegisterViewModel { Teams = teams });
}
//
// POST: /Users/Create
[HttpPost]
public async Task<ActionResult> Create(RegisterViewModel userViewModel, params string[] selectedRoles)
{
IdentityDB _db = new IdentityDB();
Team userTeam = _db.Teams.First(x => x.Name == userViewModel.SelectedTeam);
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = userViewModel.Email,
Email = userViewModel.Email,
firstName = userViewModel.firstName,
lastName = userViewModel.lastName,
number = userViewModel.number,
successfulLogins = 0,
tempPassword = true
};
user.Teams.Add(userTeam);
var adminresult = await UserManager.CreateAsync(user, userViewModel.Password); // CODE FAILS HERE
//Add User to the selected Roles
if (adminresult.Succeeded)
{
if (selectedRoles != null)
{
var result = await UserManager.AddToRolesAsync(user.Id, selectedRoles);
if (!result.Succeeded)
{
ModelState.AddModelError("", result.Errors.First());
ViewBag.RoleId = new SelectList(await RoleManager.Roles.ToListAsync(), "Name", "Name");
return View();
}
}
}
else
{
ModelState.AddModelError("", adminresult.Errors.First());
ViewBag.RoleId = new SelectList(RoleManager.Roles, "Name", "Name");
return View();
}
// Various email related activities that work fine and are unrelated to this error.
}
ViewBag.RoleId = new SelectList(RoleManager.Roles, "Name", "Name");
return View();
}
The definition of the CreateAsync method that the code fails at is:
[AsyncStateMachine(typeof(UserManager<,>.<CreateAsync>d__d))]
[DebuggerStepThrough]
public virtual Task<IdentityResult> CreateAsync(TUser user, string password);
The SQL Express database does contain a table called "ApplicationUserTeams" with the 'TeamId' and 'ApplicationUser_Id' columns created, so it seems like EF is doing its job though something is failing along the way.
Steps taken to troubleshoot at this point include deleting the entire database and the migration folder in the project, then running the application again so it creates a fresh instance, along with a new "initial" migration. This did not have any effect on the issue. I really don't know where to go from here so any help would be greatly appreciated. Thank you in advance for your time.
EDIT: In working on an Index view for the "Team" controller, I noticed I am getting an error 'Invalid object name 'dbo.ApplicationUserTeams'. This was a result of iterating over the foreign key property "Players" which are of type Application User.
#model IEnumerable<IdentitySamplesTEST.Models.Team>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
#if (item.Players.Count == 0) #*ERROR OCCURS HERE*#
{
<td>
None.
</td>
}
else
{
foreach (var player in item.Players)
{
<td>
#Html.DisplayFor(modelItem => player.firstName)
</td>
}
}
The table in the database is actually called dbo.TeamApplicationUsers - backwards to what EF seems to be looking. I have no idea what would cause this...
I have two models as below
public class Category
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; },
[Required]
public string category { get; set; }
[Required]
public string Desc { get; set; }
}
public class Product
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; },
public int CatID { get; set; },
[ForeignKey("CatID")]
public virtual Category Category { get; set; },
[Required]
public string Desc { get; set; },
public string DisplayName
{
get
{
return string.format("{0} - {1}",this.Category.category,this.Desc);
}
}
}
This is my Edit Action
public ActionResult Edit(int id)
{
ViewBag.PossibleCategories = categoryRepository.All;
return View(productRepository.Find(id));
}
[HttpPost]
public ActionResult Edit(Product product)
{
if (ModelState.IsValid) //<== This becomes false saying category.desc is required
{
productRepository.InsertOrUpdate(product);
productRepository.Save();
return RedirectToAction("Index");
}
else
{
ViewBag.PossibleCategories = categoryRepository.All;
return View();
}
}
I have a scaffolded a Edit view of product and it shows ID and DisplayName as Readonly. All the other fields a editable.
The edit view also has the product -> category -> category has a read-only text field
#Html.TextBoxFor(model => model.Category.category, new Dictionary<string, object>() { { "readonly", "true" } })
The Post back sends this and tries to create a new category. This is not required. The category link will be carried forward using the product.CatID.
How can i display these types of fields??
When the Edit view Post back the Model state appears as invalid because the product's category's desc is null (product -> category -> desc).
if i comment out the DisplayName property in Product this issue doesn't occur.
From my understanding, this is because the DiaplayName property refers to Category property and the view view doesn't have category.desc field so when the model is created back on the POST action, the desc is not populated. Adding the category.desc field to the view is one way of solving this problem.
Is there any other method to solve this?
Note: This is not the only model i'm having this issue. There are many complex models which have the same problem and to me having these fields also included in the view would make for (1) a very cluttered view (2) the amount of data making the round trip will be high.
Simple Solution
Check for null. Really you should be making this a habit anyway.
public string DisplayName
{
get
{
if(this.Category != null)
{
return string.format("{0} - {1}",this.Category.category,this.Desc);
}
else
{
return String.Empty;
}
}
}
Complex Solution
Instead of directly using your database model in your Views another solution is to create ViewModels. These are models meant specifically for your View. As a simplified example, let's take your Product model and create a ViewModel.
Create a folder for your ViewModels
Create ViewModel files that match your Controller
Create a ViewModel that you will use in your View
Say you have a Store Controller. This would be the file structure you would create.
Models
ViewModels
StoreViewModels.cs
Inside the StoreViewModels you would create a ViewModel called ProductViewModel which you would fill in with information from Product.
public class ProductViewModel
{
public int ID { get; set; }
public string Description { get; set; }
public string DisplayName { get; set; }
public ProductViewModel() { }
public ProductViewModel(Product product)
{
this.ID = product.ID;
this.Description = product.Description;
this.DisplayName = product.DisplayName;
}
}
In your View you reference ProductViewModel instead of Product. On the receiving end you then translate the ViewModel fields back to your Model. Let me know if you have any questions.
I know this has been asked many times but I can't solve my problem which is very funny.
My model is simple:
public class RegisterModel
{
[Display(Name = "First name")]
[Required]
public string FirstName { get; set; }
[Display(Name = "Last name")]
[Required]
public string LastName { get; set; }
[Display(Name = "Email address")]
[RegularExpression(#"^([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$", ErrorMessage = "Enter valid e-mail")]
[Required(ErrorMessage = "E-mail is empty")]
public string EmailAddress { get; set; }
public System.Web.Mvc.SelectList Countries { get; set; }
}
Action:
[AllowAnonymous]
public virtual ActionResult Register(RegisterModel data)
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
ModelState.Clear();
var countries =
this.ClientRepositoryBuilder
.CountryClientRepository
.GetAllCountries();
data.Countries = new SelectList(countries, "Id", "CountryName");
return
this.View(data);
}
else
{
return
this.RedirectToAction(MVC.Home.Index());
}
}
As soon as I add Countries into my model, stuff stops working and it doesn't invoke POST Action, give me an error with firebug(it's ajax post):
No parameterless constructor defined for this object
Default Model binding can't deal with that object when trying to convert from information coming in request to RegisterModel.
Options:
separate "incoming request model" from "view model" (as it looks like you always setting that property in controller).
create custom model binding - check more MSDN: The Features and Foibles of ASP.NET MVC Model Binding, or SO When to use IModelBinder versus DefaultModelBinder.
If you want something like accepting list as request parameter - Model Binding to a List MVC 4 may have an answer.
Ok I did a mistake with type, don't even know how I did this mistake.. So I updated model with proper type for Countries and stuff works now:
[Required]
public int Countries { get; set; }
It was a mistake due mine tiredness ;)