views:

1104

answers:

2

There are few things not clear to me about ASP.NET MV2. In database I have table Contacts with several fields, and there is an additional field XmlFields of which type is xml. In that field are stored additional description fields.
There are 4 classes:

  1. Contact class which corresponds to Contact table and is defined by default when creating LINQ classes
  2. ContactListView class which inherits Contact class and has some additional properties
  3. ContactXmlView class that contains fields from XmlFields field
  4. ContactDetailsView class which merges ContactListView and ContactXmlView into one class and this one is used to display data in view pages

ContactListView class has re-defined some properties from Contact class (so that I can add [Required] filter used for validation) - but I get warning message:

'ObjectTest.Models.Contacts.ContactListView.FirstName' hides inherited member 'SA.Model.Contact.FirstName'. Use the new keyword if hiding was intended.

ContactDetailsView class is also used in a form when creating new contact and adding it to database.

I am not sure if this is correct way, and the warning message confuses me a bit. Any advise about this?

Thanks,
Ile

EDIT

According to Jakob's instructions I tried it from scratch:

[MetadataType(typeof(Person_Validation))]
    public partial class Person
    {
    }

    public class Person_Validation 
    {
        [Required]
        string FirstName { get; set; }

        [Required]
        string LastName { get; set; }

        [Required]
        int Age { get; set; }
    }

In Controller I have this:

[HttpPost]
        public ActionResult Create(Person person, FormCollection collection)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    personRepository.Add(person);
                    personRepository.Save();
                }
                catch
                {
                    return View(person);
                }
            }

            return RedirectToAction("Index");            
        }

View:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Validate.Models.Person>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
 Create
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Create</h2>

    <% using (Html.BeginForm()) {%>
        <%= Html.ValidationSummary(true) %>

        <fieldset>
            <legend>Fields</legend>           

            <div class="editor-label">
                <%= Html.LabelFor(model => model.FirstName) %>
            </div>
            <div class="editor-field">
                <%= Html.TextBoxFor(model => model.FirstName) %>
                <%= Html.ValidationMessageFor(model => model.FirstName) %>
            </div>

            <div class="editor-label">
                <%= Html.LabelFor(model => model.LastName) %>
            </div>
            <div class="editor-field">
                <%= Html.TextBoxFor(model => model.LastName) %>
                <%= Html.ValidationMessageFor(model => model.LastName) %>
            </div>

            <div class="editor-label">
                <%= Html.LabelFor(model => model.Age) %>
            </div>
            <div class="editor-field">
                <%= Html.TextBoxFor(model => model.Age) %>
                <%= Html.ValidationMessageFor(model => model.Age) %>
            </div>

            <p>
                <input type="submit" value="Create" />
            </p>
        </fieldset>

    <% } %>

    <div>
        <%= Html.ActionLink("Back to List", "Index") %>
    </div>

</asp:Content>

When posting new person with no values, nothing happens (page is just reloaded). When posting with some values, person is added to db. I have no idea what am I doing wrong.

EDIT 2

I think that problem is in LINQ which already generated partial class Person. When I define new partial class Person and click on it and "Go to definition" I get following warnings:

C:\Documents and Settings\Korisnik\My Documents\Visual Studio 2008\Projects\Validate\Validate\Models\DataClasses1.designer.cs - (78, 23) : Validate.Models.Person

C:\Documents and Settings\Korisnik\My Documents\Visual Studio 2008\Projects\Validate\Validate\Models\PersonRepository.cs - (11, 26) : Validate.Models.Person

And this is part of the class generated by LINQ:

[Table(Name="dbo.Persons")]
 public partial class Person : INotifyPropertyChanging, INotifyPropertyChanged
 {

  private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);

  private int _PersonID;

  private string _FirstName;

  private string _LastName;

  private string _Age;

                ...

I am pretty sure that this is the cause but I don't know how to solve this. Any idea?

EDIT 3
Ok, let's go from beginning... so this is folder structure:

http://img535.imageshack.us/img535/3187/70090809.gif

Dataclasses1.designer.cs:

#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.3603
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace Validate.Models
{
 using System.Data.Linq;
 using System.Data.Linq.Mapping;
 using System.Data;
 using System.Collections.Generic;
 using System.Reflection;
 using System.Linq;
 using System.Linq.Expressions;
 using System.ComponentModel;
 using System;


 [System.Data.Linq.Mapping.DatabaseAttribute(Name="Database1")]
 public partial class DataClasses1DataContext : System.Data.Linq.DataContext
 {

  private static System.Data.Linq.Mapping.MappingSource mappingSource = new AttributeMappingSource();

    #region Extensibility Method Definitions
    partial void OnCreated();
    partial void InsertPerson(Person instance);
    partial void UpdatePerson(Person instance);
    partial void DeletePerson(Person instance);
    #endregion

  public DataClasses1DataContext() : 
    base(global::System.Configuration.ConfigurationManager.ConnectionStrings["Database1ConnectionString"].ConnectionString, mappingSource)
  {
   OnCreated();
  }

  public DataClasses1DataContext(string connection) : 
    base(connection, mappingSource)
  {
   OnCreated();
  }

  public DataClasses1DataContext(System.Data.IDbConnection connection) : 
    base(connection, mappingSource)
  {
   OnCreated();
  }

  public DataClasses1DataContext(string connection, System.Data.Linq.Mapping.MappingSource mappingSource) : 
    base(connection, mappingSource)
  {
   OnCreated();
  }

  public DataClasses1DataContext(System.Data.IDbConnection connection, System.Data.Linq.Mapping.MappingSource mappingSource) : 
    base(connection, mappingSource)
  {
   OnCreated();
  }

  public System.Data.Linq.Table<Person> Persons
  {
   get
   {
    return this.GetTable<Person>();
   }
  }
 }

 [Table(Name="dbo.Persons")]
 public partial class Person : INotifyPropertyChanging, INotifyPropertyChanged
 {

  private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);

  private int _PersonID;

  private string _FirstName;

  private string _LastName;

  private string _Age;

    #region Extensibility Method Definitions
    partial void OnLoaded();
    partial void OnValidate(System.Data.Linq.ChangeAction action);
    partial void OnCreated();
    partial void OnPersonIDChanging(int value);
    partial void OnPersonIDChanged();
    partial void OnFirstNameChanging(string value);
    partial void OnFirstNameChanged();
    partial void OnLastNameChanging(string value);
    partial void OnLastNameChanged();
    partial void OnAgeChanging(string value);
    partial void OnAgeChanged();
    #endregion

  public Person()
  {
   OnCreated();
  }

  [Column(Storage="_PersonID", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
  public int PersonID
  {
   get
   {
    return this._PersonID;
   }
   set
   {
    if ((this._PersonID != value))
    {
     this.OnPersonIDChanging(value);
     this.SendPropertyChanging();
     this._PersonID = value;
     this.SendPropertyChanged("PersonID");
     this.OnPersonIDChanged();
    }
   }
  }

  [Column(Storage="_FirstName", DbType="NChar(10)")]
  public string FirstName
  {
   get
   {
    return this._FirstName;
   }
   set
   {
    if ((this._FirstName != value))
    {
     this.OnFirstNameChanging(value);
     this.SendPropertyChanging();
     this._FirstName = value;
     this.SendPropertyChanged("FirstName");
     this.OnFirstNameChanged();
    }
   }
  }

  [Column(Storage="_LastName", DbType="NChar(10)")]
  public string LastName
  {
   get
   {
    return this._LastName;
   }
   set
   {
    if ((this._LastName != value))
    {
     this.OnLastNameChanging(value);
     this.SendPropertyChanging();
     this._LastName = value;
     this.SendPropertyChanged("LastName");
     this.OnLastNameChanged();
    }
   }
  }

  [Column(Storage="_Age", DbType="NChar(10)")]
  public string Age
  {
   get
   {
    return this._Age;
   }
   set
   {
    if ((this._Age != value))
    {
     this.OnAgeChanging(value);
     this.SendPropertyChanging();
     this._Age = value;
     this.SendPropertyChanged("Age");
     this.OnAgeChanged();
    }
   }
  }

  public event PropertyChangingEventHandler PropertyChanging;

  public event PropertyChangedEventHandler PropertyChanged;

  protected virtual void SendPropertyChanging()
  {
   if ((this.PropertyChanging != null))
   {
    this.PropertyChanging(this, emptyChangingEventArgs);
   }
  }

  protected virtual void SendPropertyChanged(String propertyName)
  {
   if ((this.PropertyChanged != null))
   {
    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
   }
  }
 }
}
#pragma warning restore 1591

PersonRepository.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Validate.Models;

namespace Validate.Models
{
    [MetadataType(typeof(Person_Validation))]
    public partial class Person
    {
    }

    public class Person_Validation 
    {
        [Required]
        string FirstName { get; set; }

        [Required]
        string LastName { get; set; }

        [Required]
        int Age { get; set; }
    }

    public class PersonRepository
    {
        private DataClasses1DataContext db = new DataClasses1DataContext();

        public IQueryable<Person> FindAllPersons()
        {
            return db.Persons;
        }

        public Person GetPerson(int id)
        {
            return db.Persons.SingleOrDefault(x => x.PersonID == id);
        }

        public void Add(Person person)
        {
            db.Persons.InsertOnSubmit(person);
        }

        public void Delete(Person person)
        {
            db.Persons.DeleteOnSubmit(person);
        }

        public void Save()
        {
            db.SubmitChanges();
        }
    }
}

/Views/Person/Create.aspx:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Validate.Models.Person>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
 Create
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Create</h2>

    <%= ViewData["greska"]%>

    <% using (Html.BeginForm()) {%>
        <%= Html.ValidationSummary(true) %>

        <fieldset>
            <legend>Fields</legend>           

            <div class="editor-label">
                <%= Html.LabelFor(model => model.FirstName) %>
            </div>
            <div class="editor-field">
                <%= Html.TextBoxFor(model => model.FirstName) %>
                <%= Html.ValidationMessageFor(model => model.FirstName) %>
            </div>

            <div class="editor-label">
                <%= Html.LabelFor(model => model.LastName) %>
            </div>
            <div class="editor-field">
                <%= Html.TextBoxFor(model => model.LastName) %>
                <%= Html.ValidationMessageFor(model => model.LastName) %>
            </div>

            <div class="editor-label">
                <%= Html.LabelFor(model => model.Age) %>
            </div>
            <div class="editor-field">
                <%= Html.TextBoxFor(model => model.Age) %>
                <%= Html.ValidationMessageFor(model => model.Age) %>
            </div>

            <p>
                <input type="submit" value="Create" />
            </p>
        </fieldset>

    <% } %>

    <div>
        <%= Html.ActionLink("Back to List", "Index") %>
    </div>

</asp:Content>
+3  A: 

One way to do this is to use the MetadataTypeAttribute. This attribute allows you to associate a "buddy" class to your LINQ2SQL class. The buddy class contains the same properties as the original class but ASP.NET MVC will read the validation attributes from the buddy class. The MetadataTypeAttribute was introduced because C# does not allow you to add attributes defined in a base class - hence the error you are getting.

The following example is copied from MSDN:

[MetadataType(typeof(CustomerMetaData))]
public partial class Customer
{
}

public class CustomerMetaData
{
    // Apply RequiredAttribute
    [Required(ErrorMessage = "Title is required.")]
    public object Title { get; set; }
}

EDIT:

I am not sure your action implementation is correct. When your model state is invalid you want to show the "Create" view again. Try something along these lines instead:

[HttpPost]
public ActionResult Create(Person person, FormCollection collection)
{
    if (ModelState.IsValid)
    {
        try
        {
            personRepository.Add(person);
            personRepository.Save();
            return RedirectToAction("Index");            
        }
        catch
        {
            return View(person);
        }
    }
    else
        return View(person);     
}

EDIT 2:

Linq2Sql already created a partial Person class for you and that is the way it should be. When you create your own partial Person class, the compiler will merge your class definition with the class definition created by Linq2Sql to a single Person class. But it is extremely important that the two partial class definitions are in the same assembly and in the same namespace. So please make sure that you have put your partial Person class definition in the same namespace as the Linq2Sql Person class.


EDIT 3:

It suddenly occurred to me that the properties on your Person_Validation class are private. They need to be public. I hope that helps :-)

Jakob Christensen
I tried it, but nothing it doesn't work... I'll re-edit my question, so please check
ile
No, unfortunately it doesn't work :/ But thanks for trying to help :)
ile
I re-edited again, please check. Thanks :)
ile
EDIT 3 saves my weeeeek :D... Thanks man!
ile
My pleasure :-)
Jakob Christensen
+2  A: 

@Jakob shows the correct way to do this if you want to use your data model classes directly in your views. You might also want to consider separating your view models from your data models and adding the annotations to the view models. This wouldn't preclude you from using the metadata-based validation on your data model as well, but it would further decouple your view from your data model. I often find that I need more (or different) data in my view and rather than be tempted to extend the data model class with the non-persistent data, I find that using a separate view model and translating between the two is preferable.

tvanfosson
+1 Don't send your entities down to your views, send a "View Model".
mxmissile