How do I add an extra field using ASP.Net membership provider? - asp.net-membership

I have a basic app using the ASP.NET membership provider. By default, you can use a few fields such as username, password, remember me.
How do I add to the asp.net membership provider so I can add an additional field "address" in the register section and have it save to the database?
I currently see the following when creating a user in the account model:
public MembershipCreateStatus CreateUser(string userName, string password,
string email)
{
if (String.IsNullOrEmpty(userName))
{ throw new ArgumentException("Value cannot be null or empty.", "userName"); }
if (String.IsNullOrEmpty(password))
{ throw new ArgumentException("Value cannot be null or empty.", "password"); }
if (String.IsNullOrEmpty(email))
{ throw new ArgumentException("Value cannot be null or empty.", "email"); }
MembershipCreateStatus status;
_provider.CreateUser(userName, password, email, null, null, true,
null, out status);
return status;
}
In the create user function, I also want to save the user's "address".
In the register.aspx I added a the following:
<div class="editor-label">
<%: Html.LabelFor(m => m.Address) %>
</div>
<div class="editor-field">
<%: Html.TextAreaFor(m => m.Address) %>
</div>
Any ideas?

As jwsample noted, you can use the Profile Provider for this.
Alternatively, you could create your own table(s) that store additional user-related information. This is a little more work since you're on the hook for creating your own tables and creating the code to get and save data to and from these tables, but I find that using custom tables in this way allows for greater flexibility and more maintainability than the Profile Provider (specifically, the default Profile Provider, SqlProfileProvider, which stores profile data in an inefficient, denormalized manner).
Take a look at this tutorial, where I walk through this process: Storing Additional User Information.

You're going to want to use the Profile Provider for this, thats what it was meant for. If you modify the membership provider to add the additional field it will break the provider contract and you won't be able to switch to another in the future.
Here is the profile provider: http://msdn.microsoft.com/en-us/library/2y3fs9xs.aspx
More info: http://msdn.microsoft.com/en-us/library/014bec1k.aspx
If you are using the sql membership provider than you probably have all the table structures installed to support the profile provider as well.

To add new column named "Address":
Step 1: Models/IdentityModels.cs
Add the following code to the "ApplicationUser" class:
public string Address{ get; set; }
Step 2: Models/AccountViewModels.cs
Add the following code to the "RegisterViewModel" class:
public string Address{ get; set; }
Step 3: Views/Register.cshtml
Add Address input textbox to the view:
<div class="form-group">
#Html.LabelFor(m => m.Address, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Address, new { #class = "form-control" })
</div>
</div>
Step 4 :
Go to Tools > NuGet Manager > Package Manager Console
Step A : Type “Enable-Migrations” and press enter
Step B : Type “ Add-Migration "Address" ” and press enter
Step C : Type “Update-Database” and press enter
i.e
PM> Enable-Migrations
PM> Add-Migration "Address"
PM> Update-Database
Step 5: Controllers/AccountController.cs
Go to Register Action and add "Address= model.Address" to the ApplicationUser
i.e
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, Address= model.Address}

Related

Select element client-side validation only appears after post

I am trying to use client-side validation on a select element, but it's not working when the form initially loads.
This is the property in my Model (this is actually inside a class inside my model):
[Required(ErrorMessage="Please select an application type.")]
public override string Application
{
get
{
return base.Application;
}
set
{
base.Application = value;
}
}
This is the part of the view
<div class="field_title">
Application Type
</div>
<div class="field_validation">
#Html.ValidationMessageFor(model => model.CreatePromocode.Application)
</div>
<div class="form_field">
#Html.DropDownListFor(model => model.CreatePromocode.Application,
SharedLibrary.Entities.DropDownLists.PromocodeApplicationOptions,
"Select an application type...")
</div>
This is the dropdownlist items code, I'm doing it this way so I can use it on other applications and just change it in one place rather than in several
public class DropDownLists
{
public static List<SelectListItem> PromocodeApplicationOptions
{
get
{
return new List<SelectListItem>()
{
new SelectListItem() {
Text="Earlybird Registration", Value="Earlybird"},
new SelectListItem() {
Text="Standard Registration", Value="Standard"},
new SelectListItem() {
Text="Monday Registration", Value="Monday"},
new SelectListItem() {
Text="Tuesday Registration", Value="Tuesday"},
new SelectListItem() {
Text="Wednesday Registration", Value="Wednesday"},
new SelectListItem() {
Text="Multiple Tickets", Value="TicketMany"},
new SelectListItem() {
Text="Single Ticket",Value="TicketSingle"}
};
}
}
}
The problem I am having is that when a user first arrives on the page, the drop down list does not have any client-side validation attributes. It looks like this
<select id="CreatePromocode_Application" name="CreatePromocode.Application">
Once the form is posted however, and the ModelState is invalid because the application type hasn't been selected, the client validation then appears on the drop down list and works fine.
<select data-val="true"
data-val-required="Please select an application type."
id="CreatePromocode_Application"
name="CreatePromocode.Application"
class="valid">
How can I get client-side validation working from the beginning? I would rather not have to manually add required attributes and error messages because it kind of defeats the purpose of using data annotations.
Make sure you are initializing the CreatePromoCode property of your view model as in the default constructor of this sample:
public class YourViewModel
{
public YourViewModel()
{
CreatePromoCode = new CreatePromoCode();
}
public BaseCreatePromoCode CreatePromoCode { get; set; }
}
public class BaseCreatePromoCode
{
public virtual string Application { get; set; }
}
public class CreatePromoCode : BaseCreatePromoCode
{
[Required]
public override string Application { get; set; }
}
The HtmlHelper will use the runtime type of the CreatePromoCode property of your view model in order to get the metadata for its properties and set the data attributes for the client side validation. (So the validation will work even when the Required attribute is actually set only on the derived class.)
When there are errors after the initial posting, the MVC binder has created instances of the view model and the CreatePromoCode objects. If you render again the view for errors to be displayed, as the object has been initialized the data attributes will be set and the client validation starts working.

ASP.NET MVC 3 (Razor) form submit not working

Using Fiddler I can see that the request is not even being made but I can't see why.
Here's the form:
#using (Html.BeginForm("Index", "FileSystemChannelIndex", FormMethod.Post, new {
channelId = #Model.ChannelId }))
{
#Html.HiddenFor(model => model.Id)
#Html.HiddenFor(model => model.ChannelId)
<div class="editor-label">
Select File Source
</div>
<div class="editor-field">
#Html.DropDownListFor(
model => model.SelectedFileSourceValue,
new SelectList(Model.AvailableFilesSources, "Id", "Name"),
new { id = "selectFileSource" })
</div>
<p>
<input class="t-button" type="submit" value="Save" />
</p>
}
The View originally came from:
public ViewResult Create(int channelId)
{
var channel = this.fullUOW.GetFileSystemChannelRepository().All.Where(c => c.Id == channelId);
var vm = new FileSystemChannelIndexViewModel(channelId, new FileSystemChannelIndex());
return View("Edit", vm);
}
I've tried adding the "name" attribute to the but that didn't make any difference.
Any ideas?
EDIT: More info for Jim et al...
Domain:
public class FileSystemChannel
{
public int Id {get; set; }
public ICollection<FileSystemChannelIndex> ChannelIndexes { get; set; }
}
public class FileSystemChannelIndex
{
public int Id { get; set; }
public FileSystemChannel ParentChannel { get; set; }
}
Due to a 0...* association, in the UI we have to create a FileSystemChannel first then add a FileSystemChannelIndex to it. So that's why I pass in the channelId to the FileSystemChannelIndex Create View. When submitting the new FileSystemChannelIndex the following action should be called:
[HttpPost]
public ActionResult Index(int channelId, FileSystemChannelIndexViewModel vm)
{
//TODO: get the Channel, add the Index, save to db
return View("Index");
}
So thanks to Mark's comment it's due to a Select failing client side validation. Using IE dev tools to inspect the element:
<select name="SelectedFileSourceValue" class="input-validation-error" id="selectFileSource" data-val-required="The SelectedFileSourceValue field is required." data-val-number="The field SelectedFileSourceValue must be a number." data-val="true">
empo,
further to my comment above:
empo - can you post both public ActionResult Create(////) methods (i.e. HttpPost and HttpGet) into the question as this could highlight if the issue is related to ambiguous method signatures, which i suspect could well be the case as you are posting back the same signature as the HttpGet actionresult
try adding the appropriate HttpPost actionresult along the lines of:
[HttpPost]
public ActionResult Create(FileSystemChannelIndex domainModel)
{
if (!ModelState.IsValid)
{
return View(PopulateEditViewModel(domainModel));
}
_serviceTasks.Insert(domainModel);
_serviceTasks.SaveChanges();
return this.RedirectToAction("Edit", new {id = domainModel.ChannelId});
}
your original HttpGet (which feels 'wierd' to me):
[HttpGet]
public ViewResult Create(int channelId) {
var channel = this.fullUOW.GetFileSystemChannelRepository().All
.Where(c => c.Id == channelId);
var vm = new FileSystemChannelIndexViewModel(channelId,
new FileSystemChannelIndex());
return View("Edit", vm);
}
and inside your Edit actionresult, you'd grab the entity based on the passed in id. might work, might not. not sure without a fuller picture of your domain and logic.
obviously, your own plumbing will vary, but this should give an idea of what should be expected.
How can you have Model.Id when you are creating something? Maybe Model.Id is null and because you cannot post

Add to model in form, then redisplay form to add more

I'm new to MVC3, but so far I have managed to get along with my code just great.
Now, I would like to make a simple form, that allows the user to input a text string, representing the name of an employee. I would then like this form to be submitted and stored in my model, in a sort of list. The form should then re-display, with a for-each loop writing out my already added names. When I'm done and moving on, I need to store this information to my database.
What I can't figure out, is how to store this temporary information, until i push it to my database. Pushing everytime I submit I can do, but this has cause me alot of headaches.
Hope you guys see what I'm trying to do, and have an awesome solution for it. :)
This is a simplified version of what I've been trying to do:
Model
public class OrderModel
{
public virtual ICollection<Employees> EmployeesList { get; set; }
public virtual Employees Employees { get; set; }
}
public class Employees
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
}
View
#model OrderModel
#{
if (Model.EmployeesList != null)
{
foreach (var c in Model.EmployeesList)
{
#c.Name<br />
}
}
}
#using(Html.BeginForm())
{
#Html.TextBoxFor(m => m.Employees.Name)
<input type="submit" value="Add"/>
}
Controller
[HttpPost]
public ActionResult Index(OrderModel model)
{
model.EmployeesList.Add(model.Employees);
// This line gives me the error: "System.NullReferenceException: Object reference not set to an instance of an object."
return View(model);
}
I think you should handle this by burning the employee list into the page. Right now, you're not giving your form any way of recognizing the list.
In an EditorTemplates file named Employees:
#model Employees
#Html.HiddenFor(m => m.ID)
#Html.HiddenFor(m => m.Name);
In your view:
#using(Html.BeginForm())
{
#Html.EditorFor(m => m.EmployeesList)
#Html.TextBoxFor(m => m.Employees.Name)
<input type="submit" value="Add"/>
}
[HttpPost]
public ActionResult Index(OrderModel model)
{
if (model.EmployeesList == null)
model.EmployeesList = new List<Employees>();
model.EmployeesList.Add(model.Employees);
return View(model);
}
As an added bonus to this method, it would be easy to add ajax so the user never has to leave the page when they add new employees (You might be able to just insert a new hidden value with javascript and avoid ajax. It would depend on if you do anything other than add to your list in your post).
I think this would be a good use for TempData. You can store anything in there, kind of like the cache, but unlike the cache it only lasts until the next request. To implement this, change the action method like this (example only):
[HttpPost]
public ActionResult Index(OrderModel model)
{
dynamic existingItems = TempData["existing"];
if (existingItems != null)
{
foreach (Employee empl in existingItems)
model.EmployeesList.Add(empl );
}
model.EmployeesList.Add(model.Employees);
TempData["existing"] = model.EmployeesList;
return View(model);
}

Input with date format set from ViewModel and html class attribute

I am working with razor view engine in asp.net mvc3.
Now, I need an input for a DateTime which should display the value in a fixed format (say dd-MMM-yyyy). So I can do:
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd-MMM-yyyy}")]
public DateTime StartDate { get; set; }
And in the view:
#Html.EditorFor(model => model.StartDate)
But I need to add a class in the input. Which I think is not possible in EditorFor.
So I could use
#Html.TextBoxFor(model => model.StartDate, new { #class = "Date" })
But the display format does not work in this case.
Model can be null. So,
#Html.TextBox("StartDate", string.Format("{0:dd-MMM-yyyy}", Model.StartDate))
will throw NullReferenceException.
ophelia's way is a quicker version of mine. But this way is useful if you're going to have a few date fields in your application. So this is the way i like to do it:
-> In your solution explorer, create a folder inside Shared called "EditorTemplates"
-> In there, add a new (not strongly typed) Partial View. Call it "DateTime".
-> Open this view, and remove any code in there, and throw the following code in there:
#model System.DateTime
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { #class = "date" /* + any other html attributes you want */ })
-> In your ViewModel, your date field should be like this:
[Display(Name = "My Date:"), DisplayFormat(DataFormatString = "{0:dd-MMM-yyyy}", ApplyFormatInEditMode = true)]
public DateTime MyDate { get; set; }
-> In your main view, and any view you want to use a date field, you can just use EditorFor on the property, like so:
#Html.EditorFor(m => m.MyDate)
You can initialize the date field in your controller or set a default date in your viewmodel's constructor.
I use this and its works fine:
#Html.TextBox("ExpiryDate", String.Format("{0:ddd, dd MMM yyyy}", DateTime.Now), new { id = "expirydate" })
Hope this is what you mean/need :)

MVC3 Only posted form values retained?

I am using strongly typed views in an MVC3 web app. I've noticed that when a form is submitted, the ViewModel that is passed to the controller only has values for properties that have form elements associated with them. For instance, the example below shows a simple confirmation View with a checkbox and a phone number that the user must confirm before proceeding. When the form is submitted to the controller action, the UserConfirmed property contains a value, but the PhoneNumber property is null.
Is there any way for the ViewModel to retain all of its values or do I have to repopulate the ViewModel properties that do not have form elements associated with them?
The View
#model WebMeterReplacement.ViewModels.Appointment.ScheduleConfirmationViewModel
#using (Html.BeginForm()) {
#Html.ValidationSummary(false)
#Html.CheckBoxFor(model => model.UserConfirmed)
<span>Please confirm before proceeding</span>
<div>
Phone Number: #Model.PhoneNumber
</div>
<input type="submit" value="Confirm"/>
The Controller
[HttpPost]
public ActionResult ScheduleConfirmation(ScheduleConfirmationViewModel model)
{
if (model.UserConfirmed)
{
// add ViewModel data to repository
}
else
{
ModelState.AddModelError("ERROR", WebResources.strERROR_ConfirmSchedule);
}
return View(model);
}
Since your writing the phonenumber as output to the page it won't be automatically posted back (you've found out that part) What you can do is populate an hidden or read-only field with the phonenumber so that it will be posted back to your controller. An second option is to make a new call to your datasource and repopulate your object before saving it back to your datasource.
I generally POST back information like this in a hidden input. I personally use this heavily to pass data needed to return the user exactly where they where before pressing edit.
In your case it's as simple as
#model WebMeterReplacement.ViewModels.Appointment.ScheduleConfirmationViewModel
#using (Html.BeginForm()) {
#Html.ValidationSummary(false)
#Html.CheckBoxFor(model => model.UserConfirmed)
<span>Please confirm before proceeding</span>
<div>
#Html.HiddenFor(m => m.PhoneNumber)
Phone Number: #Model.PhoneNumber
</div>
<input type="submit" value="Confirm"/>
For future reference:
If your passing complex objects back you need one hidden field per attribute (Hiddenfor does NOT iterate)
View
WRONG
#Html.HiddenFor(m => m.PagingData)
RIGHT
#Html.HiddenFor(m => m.PagingData.Count)
#Html.HiddenFor(m => m.PagingData.Skip)
#Html.HiddenFor(m => m.PagingData.PageSize)
Action
public HomeController(AViewModel Model)
{
PagingData PagingData = Model.PagingData;
Skip = PagingData.Skip;
}
If your passing Arrays you can do it like this
View
#if (Model.HiddenFields != null)
{
foreach (string HiddenField in Model.HiddenFields)
{
#Html.Hidden("HiddenFields", HiddenField)
}
}
Action
public HomeController(AViewModel Model)
{
String[] HiddenFields = Model.HiddenFields;
}
Well, the form will only POST elements that you have created. As you found out, simply writing the phone number out to the page will not suffice. The model binder can only bind those properties which exist in the posted data.
Generally you have a couple of options here:
1) You can create Input elements for all of the properties in your model, using visible elements (like a textbox) for those properties you want to edit, and hidden elements which should be posted back but have no UI
2) Post back a partial representation of your model (as you are doing now), read the entity back in from it's data source (I assume you're using some kind of data source, EF maybe) and then alter the properties of that entity with the ones from your form.
Both scenarios are common but it really depends on the complexity of your model.
I know this thread is a bit old, but thought I'd resurrect it to get feed back on my solution to this.
I'm in a similar situation where my objects are passed to a view, and the view may only display part of that object for editing. Obviously, when the controller receives the model back from the default model binder, and values not posted back become null.. and saving this means that a DB value becomes null just because it wasn't displayed/returned from a view.
I didn't like the idea of creating a model for each view. I know it's probably the right way... but I was looking for a reusable pattern that can be implemented fairly quickly.
See the "MergeWith" method... as this would be used to take a copy of the object from the database and merge it with the one returned from the view (posted back)
namespace SIP.Models
{
[Table("agents")]
public class Agent
{
[Key]
public int id { get; set; }
[Searchable]
[DisplayName("Name")]
[Column("name")]
[Required]
[StringLength(50, MinimumLength = 4)]
public string AgentName { get; set; }
[Searchable]
[DisplayName("Address")]
[Column("address")]
[DataType(DataType.MultilineText)]
public string Address { get; set; }
[DisplayName("Region")]
[Searchable]
[Column("region")]
[StringLength(50, MinimumLength = 3)]
public string Region { get; set; }
[DisplayName("Phone")]
[Column("phone")]
[StringLength(50, MinimumLength = 4)]
public string Phone { get; set; }
[DisplayName("Fax")]
[Column("fax")]
[StringLength(50, MinimumLength = 4)]
public string Fax { get; set; }
[DisplayName("Email")]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed")]
[Column("email")]
[StringLength(50, MinimumLength = 4)]
public string Email { get; set; }
[DisplayName("Notes")]
[Column("notes")]
[DataType(DataType.MultilineText)]
public string Notes{ get; set; }
[DisplayName("Active")]
[Column("active")]
public bool Active { get; set; }
public override string ToString()
{
return AgentName;
}
public bool MergeWith(Agent a, string[] fields)
{
try
{
foreach (PropertyInfo pi in this.GetType().GetProperties())
{
foreach (string f in fields)
{
if (pi.Name == f && pi.Name.ToLower() != "id")
{
var newVal = a.GetType().GetProperty(f).GetValue(a,null);
pi.SetValue(this, newVal, null);
}
}
}
}
catch (Exception ex)
{
return false;
//todo: Log output to file...
}
return true;
}
}
}
And to use this in the controller.. you'd have something like..
[HttpPost]
public ActionResult Edit(Agent agent)
{
if (ModelState.IsValid)
{
Agent ag = db.Agents.Where(a => a.id == agent.id).ToList<Agent>().First<Agent>();
ag.MergeWith(agent, Request.Params.AllKeys);
db.Entry(ag).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(agent);
}
This way, during post back, it takes the object from the database, and updates it with object from view... but only updates the values that were posted back.. So if you have a field like "address" or something that doesn't appear in the view.. it doesn't get touched during the update.
I've tested this so far and i works for my purposes, tho i welcome any feedback as I'm keen to see how others have overcome this situation. It's a first version and i'm sure it can be implemented better like through an extension method or something.. but for now the MergeWith can be copy/pasted to each model object.
Yes, Just place hidden fields in the form for those values which you are not using and want to return to server control.
Thanks

Resources