6

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.

Simon
  • 1,814
  • 20
  • 37
  • I wasn't aware you were even "allowed" to mix WebForms with MVC objects and methods. Live and learn! – Ann L. Sep 13 '12 at 21:47
  • @AnnL. It's called Model Binding and new in ASP.net 4.5: http://www.asp.net/vnext/overview/videos/aspnet-45-web-forms-model-binding – Simon Sep 14 '12 at 09:05
  • Did you ever determine the cause of this? I am running into the same problem, but I have isolated it to my inclusion of a RequiredFieldValidator for client side val. When I have this control on the page, calling ModelState.IsValid always returns true, even if I manually change the value of the intended property to null just before calling ModelState.IsValid. It probably sounds like an odd scenario, but I discovered this by accident when working with a multi layer repository. Apparently standard webform validation controls mess with the model binding system in some way. – John Jan 11 '13 at 00:02
  • @John: In the end I just added a RequiredFieldValidator, that worked in my case. – Simon Jan 14 '13 at 01:27

4 Answers4

1

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 =)

Artur Udod
  • 4,465
  • 1
  • 29
  • 58
  • I'm referencing version 4.0. Good or bad? Playing with ConvertEmptyStringToNull made no difference. I can confirm that I'm doing a POST request. Checked with Fiddler and Chrome. – Simon Sep 07 '12 at 10:09
  • As far as i understood you have a strongly typed view with model = MyTable? – Artur Udod Sep 07 '12 at 13:01
  • I didn't use MVC. This is regular WebForms. Added a tiny bit of clarification to the question to make it clear. Sorry for the confusion. – Simon Sep 07 '12 at 15:10
1

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.

smartcaveman
  • 41,281
  • 29
  • 127
  • 212
  • It appears (according to http://stackoverflow.com/questions/7820815/how-does-the-stringlengthattribute-work) that I'd need both attributes. Tried it, but didn't change a thing. I think I'm getting a null value, because that's what the debugger tells me. Inspecting item.B after the TryModelUpdate call shows me that it's null. – Simon Sep 11 '12 at 10:51
  • @Simon, Did you set the `MinimumLength` property with `StringLengthAttribute`? Can you update the code sample to accurately reflect your current situation? – smartcaveman Sep 11 '12 at 11:48
  • @Simon, In your code sample, is the second `MyTable` class declaration actually supposed to read `MyTableMetadata`? If not, the presence of the `MetadataTypeAttribute` may be inhibiting the propagation of your constraints. – smartcaveman Sep 11 '12 at 11:49
  • @Simon, Also, what is the return value of `TryUpdateModel`, when you pass it the item? – smartcaveman Sep 11 '12 at 12:00
  • You're right: The class was supposed to be called `MetadataTypeAttribute`, I changed the code accordingly and also added the `StringLengthAttribute`. `TryUpdateModel` returns `true`. (Note: If you look at the code of this method, it ends with `return IsValid;`.) – Simon Sep 11 '12 at 13:16
1

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.

Umair Ishaq
  • 752
  • 9
  • 22
  • Thank you for the well-written answer. But I don't think you understood the original problem. Everything works when I enter some text. From that I conclude that `TryUpdateModel()` works as expected and can find the controls. This is also expected since Model Binding was designed to do exactly that. But I tried using a `BoundField` anyway, but it didn't make a difference. – Simon Sep 12 '12 at 22:31
0

public class SecurityLayer { StringBuilder SB = new StringBuilder();

    public string SecurityValidate(object OBJ)
    {
        SB.Clear();
        var context = new ValidationContext(OBJ, serviceProvider: null, items: null);
        var results = new List<ValidationResult>();

        var isValid = Validator.TryValidateObject(OBJ, context, results);

        if (!isValid)
        {
            foreach (var validationResult in results)
            {
                //    Console.WriteLine(validationResult.ErrorMessage);
                SB.AppendLine(validationResult.ErrorMessage);
            }
        }

        return (SB.Length == 0 ? "SUCCESS" : SB.ToString());
    }
}