Andy
Andy

Reputation: 646

MVC Identity wants to insert Null into table when value is not Null

I just recently posted a question regarding adding properties to ApplicationUsers, and now this question follows from that:

So now the FirstName and LastName properties for ApplicationUser are fields in the AspNetUsers table. But when I go to register as a new user, I get this error:

System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'LastName', table 'aspnet-CustomUserProps-20151019035309.dbo.AspNetUsers'; column does not allow nulls. INSERT fails.

This is my Register Post in AccountController:

Public Async Function Register(model As RegisterViewModel) As Task(Of ActionResult)
    If ModelState.IsValid Then
        Dim user = New ApplicationUser() With {
            .UserName = model.Email,
            .Email = model.Email,
            .FirstName = model.FirstName,
            .LastName = model.LastName
        }
        Dim result = Await UserManager.CreateAsync(user, model.Password)

The FirstName and LastName properties of user DO contain the expected values, but this error occurs on the "Dim result =..." line.

I added fields for First Name and Last Name in the Register View like so:

<div class="form-group">
    @Html.LabelFor(Function(m) m.Email, New With {.class = "col-md-2 control-label"})
    <div class="col-md-10">
        @Html.TextBoxFor(Function(m) m.Email, New With {.class = "form-control"})
    </div>
</div>
<div class="form-group">
    @Html.LabelFor(Function(m) m.FirstName, New With {.class = "col-md-2 control-label"})
    <div class="col-md-10">
        @Html.TextBoxFor(Function(m) m.FirstName, New With {.class = "form-control"})
    </div>
</div>
<div class="form-group">
    @Html.LabelFor(Function(m) m.LastName, New With {.class = "col-md-2 control-label"})
    <div class="col-md-10">
        @Html.TextBoxFor(Function(m) m.LastName, New With {.class = "form-control"})
    </div>
</div>

And to the Register View Model like so:

<Required>
<Display(Name:="First Name")>
Public Property FirstName As String

<Required>
<Display(Name:="Last Name")>
Public Property LastName As String

What else am I missing?

Here's the top (last) several lines of the stack trace, if that helps:

[SqlException (0x80131904): Cannot insert the value NULL into column 'LastName', table 'aspnet-CustomUserProps-20151019035309.dbo.AspNetUsers'; column does not allow nulls. INSERT fails. The statement has been terminated.]
System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action'1 wrapCloseInAction) +1787814
System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action'1 wrapCloseInAction) +5341674 System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) +546
System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) +1693
System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString) +275
System.Data.SqlClient.SqlCommand.CompleteAsyncExecuteReader() +220
System.Data.SqlClient.SqlCommand.EndExecuteNonQueryInternal(IAsyncResult asyncResult) +738
System.Data.SqlClient.SqlCommand.EndExecuteNonQueryAsync(IAsyncResult asyncResult) +147

Upvotes: 0

Views: 2502

Answers (2)

Andy
Andy

Reputation: 646

Thank you to A. Burak Erbora - your suggestion did set me on the right path. But I didn't do it exactly the same...it's a bit different:

Starting a new project from scratch, first I created a new user class and context, but without the OnModelCreating function:

Imports Microsoft.AspNet.Identity.EntityFramework
Imports System.Threading.Tasks
Imports System.Security.Claims
Imports Microsoft.AspNet.Identity

Public Class AppUser
    Inherits IdentityUser

    Private m_FirstName As String
    Public Property FirstName() As String
        Get
            Return m_FirstName
        End Get
        Set(value As String)
            m_FirstName = value
        End Set
    End Property

    Private m_LastName As String
    Public Property LastName() As String
        Get
            Return m_LastName
        End Get
        Set(value As String)
            m_LastName = value
        End Set
    End Property

    Public Async Function GenerateUserIdentityAsync(manager As UserManager(Of AppUser)) As Task(Of ClaimsIdentity)
        ' Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        Dim userIdentity = Await manager.CreateIdentityAsync(Me, DefaultAuthenticationTypes.ApplicationCookie)
        ' Add custom user claims here
        Return userIdentity
    End Function
End Class

Public Class MyAppIdentityDbContext
    Inherits IdentityDbContext(Of AppUser)

    Public Sub New()
        MyBase.New("IdentityConnection", throwIfV1Schema:=False)
    End Sub

    Public Shared Function Create() As MyAppIdentityDbContext
        Return New MyAppIdentityDbContext()
    End Function
End Class

Then I deleted IdentityModels (AppUser replaces it).

Did a Project-wide Replace All of "ApplicationUser" to "AppUser".

Then a Project-wide Replace all of "ApplicationDbContext" to "MyAppIdentityDbContext".

Made sure in Web.config the name of connectionString is same as for the new context ("IdentityConnection").

Modified RegisterViewModel, Register View and Account Controller to include the fields added to AppUser.

From there all the users stuff works the same. For example, I wanted to show the first name in the navigation bar instead of the email address when a user is logged in, so this is the _LoginPartial view:

@Imports Microsoft.AspNet.Identity
@Code
    Dim db = New MyAppIdentityDbContext
    Dim curUserID = User.Identity.GetUserId()
    Dim myFirstName As String = (From users In db.Users Where users.Id = curUserID Select users.FirstName).FirstOrDefault
End Code

@If Request.IsAuthenticated
    @Using Html.BeginForm("LogOff", "Account", FormMethod.Post, New With { .id = "logoutForm", .class = "navbar-right" })
        @Html.AntiForgeryToken()
        @<ul class="nav navbar-nav navbar-right">
            <li>
                @Html.ActionLink("Hello " + myFirstName + "!", "Index", "Manage", routeValues:=Nothing, htmlAttributes:=New With {.title = "Manage"})
            </li>
            <li><a href="javascript:document.getElementById('logoutForm').submit()">Log off</a></li>
        </ul>
    End Using
Else
    @<ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink("Register", "Register", "Account", routeValues := Nothing, htmlAttributes := New With { .id = "registerLink" })</li>
        <li>@Html.ActionLink("Log in", "Login", "Account", routeValues := Nothing, htmlAttributes := New With { .id = "loginLink" })</li>
    </ul>
End If

Upvotes: 0

A. Burak Erbora
A. Burak Erbora

Reputation: 1064

It's got to be the way in which you add your extra column to the Identity db.

you need to extend the IdentityUser and redifine your DbContext by extending from IdentityDbContext with your new, extended IdentityUser class as its generic parameter. Here's how you do it:

Public Class YourApplicationUser
Inherits IdentityUser
Public Property LastName() As String
    Get
        Return m_LastName
    End Get
    Set
        m_LastName = Value
    End Set
End Property
Private m_LastName As String

Public Function GenerateUserIdentityAsync(manager As UserManager(Of YourApplicationUser)) As Task(Of ClaimsIdentity)
    ' Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
    Dim userIdentity = Await manager.CreateIdentityAsync(Me, DefaultAuthenticationTypes.ApplicationCookie)
    ' Add custom user claims here
    Return userIdentity
End Function
End Class

Public Class YourApplicationIdentityDbContext
Inherits IdentityDbContext(Of YourApplicationUser)
Public Sub New()

    MyBase.New("IdentityConnection", throwIfV1Schema := False)
End Sub

Public Shared Function Create() As YourApplicationIdentityDbContext
    Return New YourApplicationIdentityDbContext()
End Function

Protected Overrides Sub OnModelCreating(modelBuilder As System.Data.Entity.DbModelBuilder)
    MyBase.OnModelCreating(modelBuilder)

    modelBuilder.Entity(Of YourApplicationIdentityDbContext)().ToTable("IdentityUser").[Property](Function(p) p.Id).HasColumnName("UserId")

    modelBuilder.Entity(Of IdentityUserRole)().ToTable("IdentityUserRole").HasKey(Function(p) New From { _
        p.RoleId, _
        p.UserId _
    })

    modelBuilder.Entity(Of IdentityUserLogin)().ToTable("IdentityUserLogin").HasKey(Function(p) New From { _
        p.LoginProvider, _
        p.ProviderKey, _
        p.UserId _
    })

    modelBuilder.Entity(Of IdentityUserClaim)().ToTable("IdentityUserClaim").HasKey(Function(p) p.Id).[Property](Function(p) p.Id).HasColumnName("UserClaimId")

    modelBuilder.Entity(Of IdentityRole)().ToTable("IdentityRole").[Property](Function(p) p.Id).HasColumnName("RoleId")
End Sub
End Class

Note that this also allows you to change your db table names into whatever you choose - since you override with your own DbContext.

Upvotes: 1

Related Questions