Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ModelState.IsValid returns true, even if required string is null

The situation

I have a LINQ-TO-SQL model with a table that contains three columns. The usual ID (identity, autogenerated), then A of type int and B of type varchar(MAX). All columns are defined in the database as NOT NULL.

On a WebForms-page I declared an DetailsView that binds to the items:

<asp:ValidationSummary runat="server" ShowModelStateErrors="true" />
<asp:DetailsView DataKeyNames="Id" runat="server" ItemType="test.MyTable"
    SelectMethod="..." UpdateMethod="...">
      <Fields>
        <asp:DynamicField DataField="A" />
        <asp:TemplateField>
            <EditItemTemplate>
                <asp:TextBox ID="B" runat="server" TextMode="MultiLine" Columns="80" Rows="8" Text="<%# BindItem.B %>" />
            </EditItemTemplate>
            <ItemTemplate>
                <pre><asp:Label runat="server" Text="<%# Item.B %>"  /></pre>
            </ItemTemplate>
        </asp:TemplateField>
      </Fields>
</asp:DetailsView>

All this code runs in Visual Studio 2012, on ASP.NET 4.5.

What happens

So far so good. Updating and viewing works. The problem begins when I enter invalid values. Using "" for field A results in a nice error message at the top of the page. Yay! However, when I use "" as value for field B this happens:

    public void DetailsView_UpdateItem(int id)
    {
      var item = db.MyTable.Where(row => row.Id==id).Single();
      TryUpdateModel(item);
      if (ModelState.IsValid)
        db.SubmitChanges();
    }
  1. The UpdateItem method is called
  2. TryUpdateModel works and fills the form values into item.
  3. When debugging I can see that item.B is null, as expected
  4. Then ModelState.IsValid returns true?!?
  5. The call to db.SubmitChanges() fails with an exception

The problem

I do not understand why ModelState.IsValid would return true when the data model specified that B cannot be null. Can somebody explain? What am I doing wrong?

Workarounds

One thing I tried was adding Data Annotations to the data classes by including the following code. It didn't work.

  [MetadataType(typeof(MyTableMetadata))]
  public partial class MyTable
  {
  }

  public class MyTableMetadata
  {
    [Required]
    [StringLength(255,MinimumLength=1)]
    public string B { get; set; }
  }

It's obvious that I could manually check for null in the UpdateItem method or that I could add a RequiredFieldValidator - but I'd prefer not to.

like image 955
Simon Avatar asked Sep 05 '12 01:09

Simon


3 Answers

Just some arbitrary suggestions: Ensure, that you are referencing System.ComponentModel.DataAnnotations 3.5. (not 3.6) or some other correct namespace for that (as far as i got it, there are some for WCF RIA services only). try different setting for [DisplayFormat(ConvertEmptyStringToNull = ... )] Ensure that you're doing a POST request =)

like image 196
Artur Udod Avatar answered Nov 15 '22 09:11

Artur Udod


Why do you think you are getting a null value? An empty string is not equivalent to null. You can confirm this by manually checking for null, like you suggested.

You can add a StringLengthAttribute to enforce a non-empty constraint in addition to a non-null constraint.

like image 44
smartcaveman Avatar answered Nov 15 '22 09:11

smartcaveman


The TryUpdateModel() method essentially (simplifying a lot) uses the name of the field/property in the model class to look for the posted value in forms collection of the post. In the case of an asp.net grid with you specifying the ID = B for the textbox, the name of the control becomes something like ctl00$B. The model binder is not going to be able to connect the dots and obtain the value from the forms collection even though it is present in the post.

I feel that using "asp:BoundField" for your editable field might solve your problem. You may also consider writing a custom model binder or assign the value yourself. Changing the name of the property in the model to match the name of the generated control is another way to solve the problem (Please, don't pick this option though.)

TryUpdateModel() would return false when it finds the value in the post but couldn't cast the value to the type in the model. Otherwise it returns true.

like image 24
Umair Ishaq Avatar answered Nov 15 '22 09:11

Umair Ishaq