Archive for the ‘Dynamic Data’ Category

Peter Blum’s new blog and his cool new data source controls

Peter Blum has been well known is the ASP.NET world for many years for writing a whole suite of powerful controls, which you can read all about on his site.  One thing that was missing on Peter’s resume is that he never had a blog.  Well he started one earlier this month, and is making up for the lost time in a big way, with already 11 posts!  And we’re not talking about small posts that just point to other people’s stuff (unlike this post I suppose!), but real with useful meaty content.  Make sure you check out his blog at http://weblogs.asp.net/peterblum/.  I hope he keeps the good stuff coming!

In particular, Peter has been working hard on some interesting data source controls that work with Visual Studio 2010.  He’s calling them the ‘Versatile DataSources’, and is making it all available for free on CodePlex.

The following posts on his blog describe the data source controls:

The simplest way to try out his controls is to download them from CodePlex.  The package contains a rich set of samples that you can directly run and play with.  You’ll need VS2010 Beta 2 to run this, so if you don’t already have it, get it from here.

I haven’t fully tried everything yet, but the one I played with the most is his POCODataSource, which is quite interesting.  The core idea is very simple: you give it a type and it makes it easy to put up a WebForms UI to fill up an instance of that type.  The UI supports full validation using standard model annotations supported by Dynamic Data.

The beauty is that it’s really quite easy to use.  The data source declaration looks something like this (borrowed from Peter’s samples):

   <poco:POCODataSource ID="POCODataSource1" runat="server" POCOTypeName="CEOEmailGenerator" />

 

For the actual UI, you can use any standard ASP.NET data control like DetailsView, FormView or some similar 3rd party control.  Then when an Update operation happens, you simply access the built instance from the data source using  POCODataSource1.POCOInstance.  At that point, you can do whatever you want with it.  In Peter’s sample, he ends up calling an action method directly on the object, e.g.

protected void FormView1_ItemUpdated(object sender, FormViewUpdatedEventArgs e) {
   if (Page.IsValid) {
      ((CEOEmailGenerator)POCODataSource1.POCOInstance).Send();
   }
}

But I don’t see anything that ties you to this pattern, and you could instead just call some helper method and pass the object if your object doesn’t have an action method itself.

Anyway, check it out in much more details on Peter’s blog!

Using an Associated Metadata Class outside Dynamic Data

A while back, I blogged about how ASP.NET Dynamic Data apps can uses an Associated Metadata class (aka a ‘buddy’ class) to add metadata attributed to properties defined in a generated class.  It’s a mostly ugly thing that was made necessary by limitations of the C# and VB.NET languages: they don’t let you add attributes to properties defined in another partial class.


What I didn’t mention there is that this ‘buddy’ class mechanism is actually not specific to Dynamic Data apps, and can in fact be used anywhere.  Since I’ve recently heard of several cases of users trying to do something similar, I’ll describe how it’s done.  If you’re familiar with TypeDescriptionProviders (which have been around since ancient times), this will look very trivial.


I will illustrate this in a very simple console app to keep all other distractions out of the picture (the full sample is attached to the post).  So the general scenario is that we have a generated class somewhere, e.g.

// Assume that this is generated code that should never be hand modified.
// Hence metadata attributes can’t be added directly here

public partial class Product {
public string Name { get; set; }

public int UnitsInStock { get; set; }
}


Instead, the buddy provider let’s you write:

[MetadataType(typeof(Product_Metadata))]
public partial class Product {
}

class Product_Metadata {
[DisplayName("The Units In Stock")]
public object UnitsInStock { get; set; }
}


This works in Dynamic Data, but if we’re in some other context, no one will find our Product_Metadata ‘buddy’.  In our to hook it up ourselves, we just need to make one call!

TypeDescriptor.AddProvider(
new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Product)),
typeof(Product));

Pretty trivial stuff: we instantiate a TypeDescriptionProvider (with a somewhat scary name, I’ll give you that) and we register it by calling TypeDescriptor.AddProvider.


Once we do that, the attributes on the ‘buddy’ class magically show up as if they were defined on the real class:

// Get the property descriptor for UnitsInStock
PropertyDescriptor propDesc = TypeDescriptor.GetProperties(
typeof(Product)).Find(“UnitsInStock”, true);

// Get the display name attribute, which is not actually on the property,
// but on its counterpart in the ‘buddy’ class
var displayName = propDesc.Attributes.OfType<DisplayNameAttribute>().First();


And that’s pretty much it.  One important thing to notice here is that we are not using the standard reflection API (which would look like typeof(Product).GetCustomAttributes()), but we are instead using the TypeDescriptor API from the System.ComponentModel model namespace.


So to summarize, you can easily use this ‘buddy’ class mechanism anytime you deal with generated code that you need to annotate with attributes.  And even though it’s not pretty to have to use that extra class, it’s comes very handy when there is no alternative.  Maybe the day will come when C# will support doing this more cleanly, but in the meantime that’ll have to do!

Setting font attributes with UIHint in your Entity Partial Class

I’ve written a simple Field Template ( RedBold.ascx ) that reads most font attributes and applies them to your field values. The new entity templates make it easy to apply font attributes to the field labels. The image below shows several font attribut…

Setting font attributes with UIHint in your Entity Partial Class

I’ve written a simple Field Template ( RedBold.ascx ) that reads most font attributes and applies them to your field values. The new entity templates make it easy to apply font attributes to the field labels. The image below shows several font attribut…

Dynamic Data FAQ

Please post corrections/new submissions to the Dynamic Data Forum. Put FAQ Submission/Correction in your title.
See Tips on getting your ASP.NET Dynamic Data questions answered quickly
Post LINQ To SQL Questions herePost Entity Framework Questions here…

Dynamic Data FAQ

Please post corrections/new submissions to the Dynamic Data Forum. Put FAQ Submission/Correction in your title.
See Tips on getting your ASP.NET Dynamic Data questions answered quickly
Post LINQ To SQL Questions herePost Entity Framework Questions here…

Custom Validation Error message not displayed

 


A customer pointed out that his Spanish validation error messages were not being displayed; the default Error message was shown instead.  I’ll use the Products table from the NorthWind Db to reproduce the problem and show a work-around.


Create a partial class for the Products entity:

 [MetadataType(typeof(ProductsMD))]
public partial class Products {
public class ProductsMD {
[DataType("dummy", ErrorMessage = "El campo debe ser un entero positivo")]
[DisplayName("En la acción")]
public object UnitsInStock { get; set; }
}
}
VB
    <MetadataType(GetType(ProductsMD))> _
Public Class Products
Public Class ProductsMD
<DataType(“dummy”, ErrorMessage:=“El campo debe ser un entero positivo”), _
DisplayName(“En la acción”)> _
Public Property UnitsInStock() As Object
End Property
End Class
End Class

Navigate to the Products table, edit a row, and change the UnitsInStock to a letter. Select update and you will get a default error message, not the message applied with the DataType attribute.



The example above was doomed to failure as the DataType attribute above does not match the DataType enum or have a matching field template.


There are a couple of possible solutions for displaying custom validation errors on integer conversion failure. You can add the compare validator message that will be displayed for any validation error. The code should be added as the last statement of the Page_Load method in the DynamicData\FieldTemplates\Integer_Edit.ascx.cs  (or .vb) file.


CompareValidator1.ErrorMessage = String.Format(“El campo {0} debe ser un entero”, Column.DisplayName);


or for VB


CompareValidator1.ErrorMessage = String.Format(“El campo {0} debe ser un entero”, Column.DisplayName)


Entering a non-integer such as Z to the units in stock field now yields the following error message:


El campo En la acción debe ser un entero


Adding the CompareValidator1.ErrorMessage to the integer template works for every scaffolded integer.


For more flexibility, you can extract the custom message from the data model partial class. Replace the CompareValidator1.ErrorMessage  line above with the following in the Page_Load method.

 var dataTypeAttribute = MetadataAttributes.OfType<DataTypeAttribute>().SingleOrDefault();

if (dataTypeAttribute != null) {
CompareValidator1.ErrorMessage = dataTypeAttribute.FormatErrorMessage(Column.DisplayName);
}

VB

Dim dataTypeAttribute = MetadataAttributes.OfType(Of DataTypeAttribute)().SingleOrDefault() 

If dataTypeAttribute IsNot Nothing Then
CompareValidator1.ErrorMessage = dataTypeAttribute.FormatErrorMessage(Column.DisplayName)
End If

Even simpler than adding a dummy attribute and extracting the error message;  use the RegularExpression attribute and allow only positive integers. The following snippet fixes the error message problem without requiring you to extract the error message in the Page_Load method.

  [MetadataType(typeof(ProductsMD))]
public partial class Products {
public class ProductsMD {
// [DataType("dummy", ErrorMessage = "El campo debe ser un entero positivo")]
[RegularExpression(@"^0*[1-9][0-9]*$”, ErrorMessage = “Must be a entero positivo”)]
[DisplayName("En la acción")]
public object UnitsInStock { get; set; }
}
}
VB
<MetadataType(GetType(ProductsMD))> _
Public Partial Class Products
Public Class ProductsMD
Private _UnitsInStock As Object
‘ [DataType("dummy", ErrorMessage = "El campo debe ser un entero positivo")]
<RegularExpression(“^0*[1-9][0-9]*$”, ErrorMessage := “Must be a entero positivo”)> _
<DisplayName(“En la acción”)> _
Public Property UnitsInStock() As Object
Get
Return _UnitsInStock
End Get
Set(ByVal value As Object)
_UnitsInStock = value
End Set
End Property
End Class
End Class

Notes:



  1. We are using the DataType attribute as a dummy attribute so we can extract our custom error message when a validation error occurs.
  2. This is a known bug that should be fixed in the next release.

Custom Validation Error message not displayed

 


A customer pointed out that his Spanish validation error messages were not being displayed; the default Error message was shown instead.  I’ll use the Products table from the NorthWind Db to reproduce the problem and show a work-around.


Create a partial class for the Products entity:

 [MetadataType(typeof(ProductsMD))]
public partial class Products {
public class ProductsMD {
[DataType("dummy", ErrorMessage = "El campo debe ser un entero positivo")]
[DisplayName("En la acción")]
public object UnitsInStock { get; set; }
}
}
VB
    <MetadataType(GetType(ProductsMD))> _
Public Class Products
Public Class ProductsMD
<DataType(“dummy”, ErrorMessage:=“El campo debe ser un entero positivo”), _
DisplayName(“En la acción”)> _
Public Property UnitsInStock() As Object
End Property
End Class
End Class

Navigate to the Products table, edit a row, and change the UnitsInStock to a letter. Select update and you will get a default error message, not the message applied with the DataType attribute.



The example above was doomed to failure as the DataType attribute above does not match the DataType enum or have a matching field template.


There are a couple of possible solutions for displaying custom validation errors on integer conversion failure. You can add the compare validator message that will be displayed for any validation error. The code should be added as the last statement of the Page_Load method in the DynamicData\FieldTemplates\Integer_Edit.ascx.cs  (or .vb) file.


CompareValidator1.ErrorMessage = String.Format(“El campo {0} debe ser un entero”, Column.DisplayName);


or for VB


CompareValidator1.ErrorMessage = String.Format(“El campo {0} debe ser un entero”, Column.DisplayName)


Entering a non-integer such as Z to the units in stock field now yields the following error message:


El campo En la acción debe ser un entero


Adding the CompareValidator1.ErrorMessage to the integer template works for every scaffolded integer.


For more flexibility, you can extract the custom message from the data model partial class. Replace the CompareValidator1.ErrorMessage  line above with the following in the Page_Load method.

 var dataTypeAttribute = MetadataAttributes.OfType<DataTypeAttribute>().SingleOrDefault();

if (dataTypeAttribute != null) {
CompareValidator1.ErrorMessage = dataTypeAttribute.FormatErrorMessage(Column.DisplayName);
}

VB

Dim dataTypeAttribute = MetadataAttributes.OfType(Of DataTypeAttribute)().SingleOrDefault() 

If dataTypeAttribute IsNot Nothing Then
CompareValidator1.ErrorMessage = dataTypeAttribute.FormatErrorMessage(Column.DisplayName)
End If

Even simpler than adding a dummy attribute and extracting the error message;  use the RegularExpression attribute and allow only positive integers. The following snippet fixes the error message problem without requiring you to extract the error message in the Page_Load method.

  [MetadataType(typeof(ProductsMD))]
public partial class Products {
public class ProductsMD {
// [DataType("dummy", ErrorMessage = "El campo debe ser un entero positivo")]
[RegularExpression(@"^0*[1-9][0-9]*$”, ErrorMessage = “Must be a entero positivo”)]
[DisplayName("En la acción")]
public object UnitsInStock { get; set; }
}
}
VB
<MetadataType(GetType(ProductsMD))> _
Public Partial Class Products
Public Class ProductsMD
Private _UnitsInStock As Object
‘ [DataType("dummy", ErrorMessage = "El campo debe ser un entero positivo")]
<RegularExpression(“^0*[1-9][0-9]*$”, ErrorMessage := “Must be a entero positivo”)> _
<DisplayName(“En la acción”)> _
Public Property UnitsInStock() As Object
Get
Return _UnitsInStock
End Get
Set(ByVal value As Object)
_UnitsInStock = value
End Set
End Property
End Class
End Class

Notes:



  1. We are using the DataType attribute as a dummy attribute so we can extract our custom error message when a validation error occurs.
  2. This is a known bug that should be fixed in the next release.

Explicit connection string for EF

The default constructor for the ObjectContext class in the  Entity Data Model (EDM) retrieves the construction string from the web.config or app.config file. If you have multiple data models or need to pass in the the construction string at run time you must use the ObjectContext constructor that takes a construction string. (Note: Be sure to see the bug/work-around at the end of this article.) The follow snippet shows how to build the string for integrated security.

//  copy connection string from app.config or web.config  

// connectionString=”metadata=res://*;
// provider=System.Data.SqlClient;
// provider connection string=&quot;
// Data Source=ricka0;Initial Catalog=Northwind;Persist Security Info=True;
// User ID=sa;Password=*(IU89iu;MultipleActiveResultSets=True&quot;”
// providerName=”System.Data.EntityClient”

public static string UglyConStr() {

return “metadata=res://*;”
+ “provider=System.Data.SqlClient;”
+ “provider connection string=’;” // Replace &quot with ‘ (single quote)
+ “Data Source=ricka0;”
+ “Initial Catalog=Northwind;”
+ “Persist Security Info=True;”
+ “User ID=sa;Password=*(IU89iu;”
+ “MultipleActiveResultSets=True’;” // Replace &quot with ‘ (single quote)
// + “providerName=\”System.Data.EntityClient\”"
;
}

While the construction string above works, it’s very ugly. The hair pulling trick to get it working is replacing &quot with a single quote as shown in the comments. Using  the raw connection string from the config file (using &quot ) results in a misleading error message Keyword not supported: ‘data source’.  


A much more elegant construction string using SQL connection is shown below.

 public static string getConStrSQL() {

string connectionString = new System.Data.EntityClient.EntityConnectionStringBuilder
{
Metadata = “res://*”,
Provider = “System.Data.SqlClient”,
ProviderConnectionString = new System.Data.SqlClient.SqlConnectionStringBuilder
{
InitialCatalog = “Northwind”,
DataSource = “ricka0″,
IntegratedSecurity = false,
UserID = getUID(), // User ID such as “sa”
Password = getPWD(), // hide the password
}.ConnectionString
}.ConnectionString;

return connectionString;
}

In VB:

Public Shared Function getConStrSQL() As String
Dim connectionString As String = New System.Data.EntityClient.EntityConnectionStringBuilder() _
With {.Metadata = “res://*”, _
.Provider = “System.Data.SqlClient”, _
.ProviderConnectionString = New System.Data.SqlClient.SqlConnectionStringBuilder() _
With {.InitialCatalog = “Northwind”, _
.DataSource = “ricka0″, _
.IntegratedSecurity = False, _
.UserID = getUID(), _
.Password = getPWD()}.ConnectionString}.ConnectionString
Return connectionString
End Function

The integrated security approach is slightly different.

public static string getConStrIntegrated() {

string conStrIntegratedSecurity = new System.Data.EntityClient.EntityConnectionStringBuilder
{
Metadata = “res://*”,
Provider = “System.Data.SqlClient”,
ProviderConnectionString = new System.Data.SqlClient.SqlConnectionStringBuilder
{
InitialCatalog = “NorthwindEF”,
DataSource = “bing0″,
IntegratedSecurity = true,
}.ConnectionString
}.ConnectionString;

return conStrIntegratedSecurity;
}

For Dynamic Data, simply pass the construction string to the MetaModel RegisterContext as follows. 
 public static void RegisterRoutes(RouteCollection routes) {
MetaModel model = new MetaModel();

model.RegisterContext(() => new NorthwindModel.NorthwindEntities(getConStrIntegrated()),
new ContextConfiguration()
{
ScaffoldAllTables = true
});

// Routes omitted for clarity
}


Unfortunately, the current version of Dynamic Data doesn’t support this approach with EF (L2S does work). To get the page templates to use the connection string you must add the following line to the Page_Load method in the page templates.

GridDataSource.ContextCreating += delegate(object ceSender, System.Web.UI.WebControls.EntityDataSourceContextCreatingEventArgs ceArgs) {
ceArgs.Context = (System.Data.Objects.ObjectContext)table.CreateContext();
};

The complete Page_Load is below

protected void Page_Load(object sender, EventArgs e) {
table = GridDataSource.GetTable();

GridDataSource.ContextCreating += delegate(object ceSender, System.Web.UI.WebControls.EntityDataSourceContextCreatingEventArgs ceArgs) {
ceArgs.Context = (System.Data.Objects.ObjectContext)table.CreateContext();
};

Title = table.DisplayName;
GridDataSource.Include = table.ForeignKeyColumnsNames;
InsertHyperLink.NavigateUrl = table.GetActionPath(PageAction.Insert);

// Disable various options if the table is readonly
if (table.IsReadOnly) {
GridView1.Columns[0].Visible = false;
InsertHyperLink.Visible = false;
}
}


Explicit connection string for EF

The default constructor for the ObjectContext class in the  Entity Data Model (EDM) retrieves the construction string from the web.config or app.config file. If you have multiple data models or need to pass in the the construction string at run time you must use the ObjectContext constructor that takes a construction string. (Note: Be sure to see the bug/work-around at the end of this article.) The follow snippet shows how to build the string for integrated security.

//  copy connection string from app.config or web.config  

// connectionString=”metadata=res://*;
// provider=System.Data.SqlClient;
// provider connection string=&quot;
// Data Source=ricka0;Initial Catalog=Northwind;Persist Security Info=True;
// User ID=sa;Password=*(IU89iu;MultipleActiveResultSets=True&quot;”
// providerName=”System.Data.EntityClient”

public static string UglyConStr() {

return “metadata=res://*;”
+ “provider=System.Data.SqlClient;”
+ “provider connection string=’;” // Replace &quot with ‘ (single quote)
+ “Data Source=ricka0;”
+ “Initial Catalog=Northwind;”
+ “Persist Security Info=True;”
+ “User ID=sa;Password=*(IU89iu;”
+ “MultipleActiveResultSets=True’;” // Replace &quot with ‘ (single quote)
// + “providerName=\”System.Data.EntityClient\”"
;
}

While the construction string above works, it’s very ugly. The hair pulling trick to get it working is replacing &quot with a single quote as shown in the comments. Using  the raw connection string from the config file (using &quot ) results in a misleading error message Keyword not supported: ‘data source’.  


A much more elegant construction string using SQL connection is shown below.

 public static string getConStrSQL() {

string connectionString = new System.Data.EntityClient.EntityConnectionStringBuilder
{
Metadata = “res://*”,
Provider = “System.Data.SqlClient”,
ProviderConnectionString = new System.Data.SqlClient.SqlConnectionStringBuilder
{
InitialCatalog = “Northwind”,
DataSource = “ricka0″,
IntegratedSecurity = false,
UserID = getUID(), // User ID such as “sa”
Password = getPWD(), // hide the password
}.ConnectionString
}.ConnectionString;

return connectionString;
}

In VB:

Public Shared Function getConStrSQL() As String
Dim connectionString As String = New System.Data.EntityClient.EntityConnectionStringBuilder() _
With {.Metadata = “res://*”, _
.Provider = “System.Data.SqlClient”, _
.ProviderConnectionString = New System.Data.SqlClient.SqlConnectionStringBuilder() _
With {.InitialCatalog = “Northwind”, _
.DataSource = “ricka0″, _
.IntegratedSecurity = False, _
.UserID = getUID(), _
.Password = getPWD()}.ConnectionString}.ConnectionString
Return connectionString
End Function

The integrated security approach is slightly different.

public static string getConStrIntegrated() {

string conStrIntegratedSecurity = new System.Data.EntityClient.EntityConnectionStringBuilder
{
Metadata = “res://*”,
Provider = “System.Data.SqlClient”,
ProviderConnectionString = new System.Data.SqlClient.SqlConnectionStringBuilder
{
InitialCatalog = “NorthwindEF”,
DataSource = “bing0″,
IntegratedSecurity = true,
}.ConnectionString
}.ConnectionString;

return conStrIntegratedSecurity;
}

For Dynamic Data, simply pass the construction string to the MetaModel RegisterContext as follows. 
 public static void RegisterRoutes(RouteCollection routes) {
MetaModel model = new MetaModel();

model.RegisterContext(() => new NorthwindModel.NorthwindEntities(getConStrIntegrated()),
new ContextConfiguration()
{
ScaffoldAllTables = true
});

// Routes omitted for clarity
}


Unfortunately, the current version of Dynamic Data doesn’t support this approach with EF (L2S does work). To get the page templates to use the connection string you must add the following line to the Page_Load method in the page templates.

GridDataSource.ContextCreating += delegate(object ceSender, System.Web.UI.WebControls.EntityDataSourceContextCreatingEventArgs ceArgs) {
ceArgs.Context = (System.Data.Objects.ObjectContext)table.CreateContext();
};

The complete Page_Load is below

protected void Page_Load(object sender, EventArgs e) {
table = GridDataSource.GetTable();

GridDataSource.ContextCreating += delegate(object ceSender, System.Web.UI.WebControls.EntityDataSourceContextCreatingEventArgs ceArgs) {
ceArgs.Context = (System.Data.Objects.ObjectContext)table.CreateContext();
};

Title = table.DisplayName;
GridDataSource.Include = table.ForeignKeyColumnsNames;
InsertHyperLink.NavigateUrl = table.GetActionPath(PageAction.Insert);

// Disable various options if the table is readonly
if (table.IsReadOnly) {
GridView1.Columns[0].Visible = false;
InsertHyperLink.Visible = false;
}
}