Showing posts with label RIA Services. Show all posts
Showing posts with label RIA Services. Show all posts

Friday, June 17, 2011

Deploying RIA Services seems a bit too complicated

I have spent many hours troubleshooting deployments my Silverlight application (server side) just to figure out that I have the wrong version of a .dll. It seems that the easiest way to guarantee that you have the correct assemblies is to open your web project and select all the References and change the Copy Local property to True. I didn’t notice more than a second or two delay in build time and then when I deploy I know that I will get all the assemblies I need.

Generally, you don’t need to worry about the references changing except when you get a service pack, a new version, upgrade, etc so if you like you can keep the Copy Local set to False most of the time. Then when your references change be sure to change the Copy Local back to True.

I’m so lost as to why this is so difficult.

If anyone has a better way, please let me know. What price can you put on sanity? Smile

Wednesday, June 15, 2011

Compiler error with VS2010 SP1 and SL 4 with WCF RIA Services and Entity Framework 4

Did you have a VS2010 project (before installing VS2010 SP1 or RIA Service Pack 1) that used Silverlight 4 and WCF RIA Services and Entity Framework 4 (EF4) and now that VS2010 Service Pack 1 is installed (or maybe some other framework) your Silverlight Project no longer compiles. Chances are if you are using RIA Services you may have created files that you want to share (called Shared Code)on the Server and the SL Client. This is done in RIA by using the .shared.cs file extension on a filename.

After I installed either VS2010 SP1 or RIA Services SP1 and then I tried to compile my project and got a compiler error similar to:

error CS0102: The type 'MyApp.Model.MyClassHere' already contains a definition for 'MyPropertyHere'

I learned that this may be a RIA Services SP1 regression bug from this forum. The beta had issues also.

It appears that this MAY be fixed now, but I’m not sure since I don’t have an issue exactly like what they had. I only have the issue if I have to shared files and one the classes (in one of the files) has a property or method that has a return type that is the other class type. If I get rid of that dependency and just return the object type instead then it compiles fine. While returning the type object, I have to cast every time I use it and that is silly and kind of a pain / ugly.

I hope that helps someone. If anyone else has this issue I’d love to know about it.

Wednesday, May 18, 2011

Programmatically adding Query Parameter to ASP.NET DomainDataSource

I love the new ASP.NET DomainDataSource because it allows me to talk to my Domain Service.

You could do this declaratively like the following:

Declaratively

<asp:DomainDataSource ID="dsDetails" runat="server">
<QueryParameters>
        <asp:ControlParameter ControlID="GridView1" PropertyName="SelectedValue" Name="id" Type="Int32" />
</QueryParameters>
</asp:DomainDataSource>

In this example, this a DomainDataSource that I have connect to a FormView. I want the FormView to update when a GridView called GridView1.SelectedValue changes. I am setting the QueryName and the DomainServiceTypeName in my Page_Init so you don’t see it in the declaration above, but you could put it there as well.

In this scenario, the Query in my Domain Service is expecting a parameter called id. This must the Name property of the ControlParameter control.

Programmatically (Good Option)

We can do the same thing with the parameters, but this time do it programmatically. The declaration would now look like this:

<asp:DomainDataSource ID="dsDetails" runat="server">
</asp:DomainDataSource>

Notice there is not QueryParameters declared, so we need to do this in code. The best place is in the Page_Init() method since it is very early in the page lifecycle.

Add the following line to the Page_Init() method:

dsDetails.QueryParameters.Add(new ControlParameter("id", TypeCode.Int32, "GridView1", "SelectedValue"));

This will do exactly the same as doing it declaratively, but now you nave more control of all the values passed since you are in code. One thing to point out is that when the GridView1.SelectedValue changes, the dsDetails datasource executes the query again automatically. That is why I like this method.

Programmatically (Okay Option)

The above is identical and great and my first choice. This approach here works best if you are getting your value from something that doesn’t change like the QueryString. If you need to set the current user, or some other value (like a querystring value) and don’t care that the dsDetails DomainDataSource will NOT be updated automatically and you will instead need to tell it to databind using something like SelectedItemChanged in the GridView1, you can use the Querying event on the DomainDataSource. In this case you would do the following.

<asp:DomainDataSource ID="dsEditor" runat="server"  
    onquerying="dsEditor_Querying" >
</asp:DomainDataSource>

protected void dsDetails_Querying(object sender, Microsoft.Web.UI.WebControls.DomainDataSourceQueryingEventArgs e)
{
    e.QueryParameters.Clear();
    e.QueryParameters.Add("id", GridView1.SelectedValue);
}

Friday, April 29, 2011

Create CRUD UI using ASP.NET 4.0, FormView, Dynamic Data, DomainDataSource, Entity Framework, WCF Domain Service, LINQ, and custom validation – Part II

This is one entry in a series of blog entries:

Adding Labels based on MetaData from Model

In Part I we did some pretty neat stuff with Dynamic Data. In this blog entry I will show how to have your labels be set to what is set in the MetaData.

Let’s do some additional clean up here. Let’s remove the ProductCategoryID and ProductModelID fields since they will never be used.

Next we need to replace static text labels with a Label control so that we can set it via code. For example, replace ProductID with

<asp:Label ID="lblProductID" runat="server" AssociatedControlID="ProductIDDynamicControl"  OnLoad="LoadLabelForDynamicControl"></asp:Label>

I recommend a consistent naming convention to make remembering control ids. Once you have replaced one template such as the EditItemTemplate you can copy it to the InsertItemTemplate and the ItemTemplate, but be sure to change the Mode property to Insert and ReadOnly respectively.

Since we will have to get the metadata for each column on the FromView we will create a method that does this for us. Below is that method.

/// <summary>
/// Assuming you have a Label and DynamicControl (with the AssoicatedControlID on the label control set to the DynamicControl)
/// on your FormView it will set the text of the label control to the DisplayName which is from the MetaColumn for the column
/// used by the DynamicControl.
/// </summary>
/// <param name="labelControl">The label control that will have its text changed</param>
private void SetLabelBasedOnMetaData(Label labelControl)
{
           
    string dynamicControlID = labelControl.AssociatedControlID;
    if (string.IsNullOrEmpty(dynamicControlID)) throw new Exception("The AssociatedControlID must be set for Label control with ID: " + labelControl.ID);

    var container = labelControl.NamingContainer;
    var userControl = container.FindControl(dynamicControlID) as DynamicControl;
    if (userControl == null) throw new Exception("Could not find dynamic control with id: " + dynamicControlID);

    labelControl.Text = userControl.Column.DisplayName;
}

This will be called from the OnLoad event for each label.

protected void LoadLabelForDynamicControl(object sender, EventArgs e)
{
    Label labelControl = sender as Label;
    SetLabelBasedOnMetaData(labelControl);
}

If you run this now you will see that there is no real difference from the user’s perspective. However, the labels are being pulled from the MetaData (the Model). Now we just need to specify the label we want to display. This is the same label that is used in generated error messages.

 

To specify the labels, open the AWDomainService.metadata.cs and find the property you want to set the label for and add the following line.

[Display(Name="Product ID")]
public int ProductID { get; set; }

Do this for each property. Re-run and this time you will see that the labels are the values you set in the Display attribute.

It is a little more work, but in the end your labels will always be in sync from screen to screen and your error messages will reflect the name as well. When you need to make a change, you just do it once in your model. It is also the same information that your scaffolded screens will use as well.

 

All source for this project can be downloaded here.

Thursday, April 28, 2011

Create CRUD UI using ASP.NET 4.0, FormView, Dynamic Data, DomainDataSource, Entity Framework, WCF Domain Service, LINQ, and custom validation – Part I

This is one entry in a series of blog entries:

The title is a mouthful and thus the topic can be kind of overwhelming. With ASP.NET 4 there are so many different technology frameworks that you need to be familiar with. The problem that I have found is that most examples only use a one or two of these frameworks. In my world, I want to use them all and the examples are never as simple as the examples. In this blog entry, I will give a read world example of how to combine all these technologies. I find myself having to figure out how to do this over and over again. Hopefully, this will be of help to others.

This blog entry is NOT about how to create a Dynamic Data project in VS2010 and create an admin UI or something like that. This is about creating a page from scratch that uses this technology. To better understand the different scenarios that Dynamic Data can be used in and steps on how to extend it, see here.

Technology Overview

I am writing in the context of ASP.NET 4.0 and VS2010 (Visual Studio 2010). These technologies are relatively new in some cases and the api’s have changed since betas. I am only showing stuff that works in VS2010 (the final released version).

  • ASP.NET 4.0 – This is the version of ASP.NET that I am targeting.
  • FormView – This is the control that we will be using as a basis for the CRUD UI (User Interface).
  • Dynamic Data – This is the technology that allows you to use DynamicControl on your FormView instead of the standard ASP.NET Forms controls. BTW, you don’t have to have anything installed (not even the Dynamic Data files and templates). You get basic implementation without them, but with them you can customize and get a more full-featured set of tools. See here for a GREAT video on using Dynamic Data in “old” applications that don’t have all these cool technologies. For the most up to date resources, click here.
  • Entity Framework 4.0 – The standard with VS2010 for database to object mapping. Click here if you want to see how to use Dynamic Data with an ObjectDataSource and GridView.
  • WCF Domain Service – This is your middle tier where you would put your business logic, complex validation, etc. It provides a consist way to access your perform CRUD operations on your data. Abstraction layer before the Entity Framework.
  • LINQ – We will use LINQ to Entities in the WCF Domain Service to query database through the Entity Framework.
  • DomainDataSource – This is a control that works much like the ObjectDataSource or LinqDataSource or EntityDataSource except that it can connect to your WCF Domain Service from ASP.NET. Please note that there is also one for Silverlight and it has the same name so when you are Googling be sure to check what the context is. This is a good doc on using this control.

Getting Started

You can select most any type of web application project in VS2010 to get started. My instructions will be for the Dynamic Data project so that I don’t have to explain how to manually move over the Dynamic Data files. If you have an existing project and need to copy over the Dynamic Data files, click here for detailed instructions. Please note, I have copied some of these steps (and written others) from instructions I have read on MSDN, but have combined them together in one continuous instruction set. For example, most of the instruction are from: here and here.

  1. Open Visual Studio 2010 and create a new project of type ASP.NET Dynamic Data Domain Service Web Application. I’m calling my project MyDynamicDataExample.
  2. Optional - Create two Directories: Models and Services.
  3. Optional - Download and install the AdventureWorksLT database if you don’t already have it. The particular database is not really that important to understand the concepts. Notice I am using the Lite version since it is a bit simpler to follow, etc.
  4. Add a New Item… and select ADO.NET Entity Data Model. I called MyModel.edmx. We will generate from Database. Select the database you want to work with (add a new connection if it is not there). On the Choose Your Database Objects screen, select Product and ProductCategory. Keep everything else default.
  5. You must build your project so that the next step will work.
  6. Add (to Services folder) a New Item… and select Domain Service Class. I called mine AWDomainService.cs. Select the options as shown below:
    image
  7. Add (to Models folder) a New Item… and select Web Form using Master Page. Call it DDTest.aspx. Select the Site.master on the next screen.
  8. From the General or Data group of the Toolbox, add a DomainDataSource control to the page. If you have never used the DomainDataSource before you will need to add it to your toolbox. To add it to your toolbox. Click the Tools menu, and then click Choose Toolbox Items. In the Choose Toolbox Items dialog box, click the .NET Framework Components tab, select the DomainDataSource check box, and then click OK.
  9. Add a DomainDataSource to the page. Configure the datasource to point to GetProducts() method on the service you created. Enable Inserts, Updates and Deletes.
  10. Add a QueryExtender to the page. Set the TargetControlID to the id of the DomainDataSource (mine is called DomainDataSource1).
  11. Add a FormView (though a GridView or DetailsView could also be used). Check the Enable Dynamic Data Support checkbox. Please note it will not stay checked when you come back to it later (in some cases). Set the Data Source to the DomainDataSource you added earlier.
  12. Add a DynamicDataManager control to the page. Chose Register Controls… and add the FormView as shown below.
    image
    This registers the data-bound control and enables dynamic behavior for it.

  13. Open the code-behind. Add a Page_Init method and add the following line:

    protected void Page_Init(object sender, EventArgs e)
    {
        FormView1.EnableDynamicData(typeof(MyDynamicDataExample.Models.Product));
    }

  14. Go to the .aspx page again and go to the Source view. Find the QueryExtender you added earlier. Add the following line to it:

    <asp:QueryExtender ID="QueryExtender1" runat="server" TargetControlID="DomainDataSource1">
            <asp:DynamicRouteExpression ColumnName="ProductID" />
    </asp:QueryExtender>

    This will allow us to pass the ID that we want to edit via the url as a query string.

  15. Now we need to do some clean up. In each of the templates in the FormView remove the Text and Dynamic controls for: ProductCategoryReference, EntityState, and EntityKey. Also, remove the rowguid from the insert template.

  16. Let’s test to see if this works now. You will see that you get the YSOD (Yellow Screen Of Death) when you click the Edit button and then the Update button. You will get an EntityOperationException and the last method that was called was HandleValidationErrors. This means that something in the model failed.

  17. Create event handlers on the DomainDataSource for the Updated and Inserted events. They should look like this when you are done:

    protected void DomainDataSource1_Inserted(object sender, Microsoft.Web.UI.WebControls.DomainDataSourceStatusEventArgs e)
    {
        if (e.ChangeSetEntry.HasError)
        {
            foreach (var error in e.ChangeSetEntry.ValidationErrors)
            {
                AddValidationSummaryItem(error.Message);
            }
        }
    }

    protected void DomainDataSource1_Updated(object sender, Microsoft.Web.UI.WebControls.DomainDataSourceStatusEventArgs e)
    {
        if (e.ChangeSetEntry.HasError)
        {
            foreach (var error in e.ChangeSetEntry.ValidationErrors)
            {
                AddValidationSummaryItem(error.Message);
            }
        }
    }

  18. Next add this supporting method.

    public void AddValidationSummaryItem(string errorMessage)
    {
        var validator = new CustomValidator();
        validator.IsValid = false;
        validator.ErrorMessage = errorMessage;
        this.Validators.Add(validator);
    }

  19. Now put a breakpoint on the Updated event handler and debug your application. Do the same test again, you’ll see there is some error about the field: ThumbnailPhoto. Note that the error does not display on the page, but it doesn’t go to the YSOD either. Like any other validation exception that is NOT column specific, we need to use the ValidationSummary control to view this error.

  20. Drag a ValidationSummary control to your page. Re-run and you’ll see the exception. This does give little bit of database schema information. This may be considered by some to be bad. I’ll leave that up to you. If you don’t do this then you won’t get validation exceptions that are thrown from your domain service which is how you will implement your business logic. My experience shows that nothing too serious comes out of here once you have everything wired up correctly. If you don’t like that you’ll have to come up with another solution.

  21. Let’s quickly fix this by removing the ThumbNailPhoto field from each of the FormView templates. Re-run, and this time you won’t get any errors. We can do this because it is an optional field.

  22. Let’s add some validation to see how to do that. Go to the AWDomainService.cs and find the UpdateProduct method. Modify it so that it looks like this:


    public void UpdateProduct(Product currentProduct)
    {
        this.ObjectContext.Products.AttachAsModified(currentProduct, this.ChangeSet.GetOriginal(currentProduct));
        if (currentProduct.ListPrice < 2000)
            throw new ValidationException("List Price is too low.");
    }
    Re-run, change the List Price to less than 2000 and update and you’ll get the message shown in the ValidationSummary control.

  23. You can also use ValidationAttributes such as Required, Range, RegularExpression, etc in the Model to declaratively validate the data. To do this open your AWDomainServices.metadata.cs. Find the StandardCost property and modify it so that it looks like this:

    [Range(0, 200, ErrorMessage="{0} must be between 0 and 200.")]
    public decimal StandardCost { get; set; }

    Re-run and and change the standard cost to something greater than 200 if it isn’t already and Update. You’ll see it says StandardCost must be between 0 and 200. Notice it filled in the name of the field for us. It is the actual property name though.

  24. To display a user-friendly version of the property name you can add Display information as shown below.

    [Display(Name="Standard Cost")]
    [Range(0, 200, ErrorMessage="{0} must be between 0 and 200.")]
    public decimal StandardCost { get; set; }

    Re-run and you’ll see the same message but with the friendly name of the property.

  25. To Edit a particular record such as the Product with ID = 800, just go to the same url, but with DDTest.aspx?ProductID=800.

  26. If you try to run your application without specifying the specific test page you will get an error like this “There are no accessible tables. Make sure that at least one data model is registered in Global.asax and scaffolding is enabled or implement custom pages.”

    To fix that you just need to go to your Global.asax.cs and uncomment the line that start with DefaultModel.RegisterContext. Change it so that it looks like this:

    DefaultModel.RegisterContext(new DomainModelProvider(typeof(MyDynamicDataExample.Services.AWDomainService)), new ContextConfiguration() { ScaffoldAllTables = true});

    When you do this you’ll have some CRUD pages for each of the tables you included. This is separate from what we are doing here which is a custom page, but let’s fix the errors anyway. Another option would be to not have this default page and leave that line above commented. That way you won’t have all these admin page open to the world. Yes you can secure them, but they are not by default.

  27. Back on track, when you access the Default.aspx page you will get an error like this: The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.

    To fix this just go to the AWDomainService.cs and add a sort method to the default query methods which are GetProducts and GetProductCategories. Just make the methods look like these:

    public IQueryable<ProductCategory> GetProductCategories()
    {
        return this.ObjectContext.ProductCategories.OrderBy(o => o.Name);
    }

    public IQueryable<Product> GetProducts()
    {
        return this.ObjectContext.Products.OrderBy(o => o.Name);
    }

  28. Re-run. This time go to the Product link. The same validation we added before is also here. This is because the validation is written at the Model, not the UI. This is AWESOME in my opinion. I love it. This is why I love Dynamic Data.

I hope this has been beneficial to all.

The entire source for this example (minus the AdventureWorksLT database) can be downloaded from here.

Tuesday, April 19, 2011

How to change the display format of a field using MetaData

Let’s assume you have a Domain Service class that you are access from your ASP.NET Dynamic Data application (should work for Silverlight client as well (I think)) and that you have a table called Person. The Person table has a field / property / column that is called BirthDate. By default it is displayed as a date AND time in the GridView and the Edit and Detail Forms. The way to change the display format is in the MetaData for the Person table. In particular the MetaData for the property BirthDate. The basic MetaData class is generated for you when you create the Domain Service class.

The question is what Attribute do we use to communicate what we want. The answer is: DataFormatAttribute. Use it like you would a string format in most other places in the .NET framework. In the example below only the date (not the time) will always be shown.

[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode=true)]
public DateTime PerformedOnDateTimeUtc { get; set; }

If we remove or set the ApplyFormatInEditMode parameter to false (as shown below) then insert and edit will still show the time, but the gridview will still only show the date (no time).

[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode=false)]
public DateTime PerformedOnDateTimeUtc { get; set; }

You can use custom date formats, use on different datatypes, etc.

Wednesday, February 9, 2011

Setting default values in Entity Framework when using Silverlight

I found myself amazed at how difficult it is to set default values for a property in the Entity Framework (even version 4) when used with Silverlight. Ideally I could use the DefaultValue attribute in the metadata class for my model. The problem is that Silverlight doesn’t use it. It seems to work ok in my tests that don’t use Silverlight if I remember correctly. But that only partially helps. I explored putting the value in the constructor for the entity that has the property I want to set the default value on, but that fires every time the object is created. This means that it was created even when displaying the results of a query, which is not the event I was hoping for. I want the default value to be used when a new entity is created, but just the object instantiated.

Then I ran across this post. It talks about creating a partial class on the client-side (Silverlight) and using the partial method called OnCreated(). The forum seems to say that you can define the OnCreated() partial method in the partial entity class that is on the server-side, but that method is not defined on the server-side that I can tell. So, the partial keyword doesn’t work in that context. The server-side object has a .shared.cs instead of just .cs extension so the file will be copied to the client-side. The problem is that the code has to work on the client and the server side. As noted before, the OnCreated method only exists on the client-side.

The solution is actually quite simple. I just had to explicitly define a partial class on the client-side even though I already have one on the server-side and is shared. This allows me to leverage the OnCreated partial method on the client that will fire when the entity is created, not just instantiated, and still keep my shared partial class that is on the server and client side.

I haven’t tried it, but by doing like Kyle recommends I should be able to use the DefaultValue attribute in the metadata and have it carry over to the client-side by calling the utility method that reads the DefaultValue attribute from the model when the OnCreated method is called.

So, here is an example, of what I am trying to explain.

In this example (to keep with the example on the forum) my entity is called Entity1

In my Silverlight project, I define a file called Entity1.cs. It would contain:

public partial class Entity1
    {
        partial void OnCreated()
        {
            DefaultValueUtility.InitializeDefaultValues(this);
        }
    }

NOTE: I could also just set the property directly instead of pulling the data from the model.

I can, but don’t have to have a file called Entity1.shared.cs on my server-side that will can contain whatever I want it to. It will be copied to the client-side as well. Then it will be merged with the Entity1.cs and the generated Entity1 file from the model itself on the client side because they are all partial classes.

Here is the code for the DefaultValueUtility call above. You will want to put this code somewhere in your Silverlight project.

using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;

namespace DefaultSetter
{
    public static class DefaultValueUtility
    {
        public static void InitializeDefaultValues(object entity)
        {
            PropertyInfo[] properties = entity.GetType().GetProperties();
            foreach (PropertyInfo property in properties)
            {
                MethodInfo propertySetter = property.GetSetMethod();
                if (propertySetter != null)
                {
                    DefaultValueAttribute defaultValueAttribute = (DefaultValueAttribute)
                        property.GetCustomAttributes(typeof(DefaultValueAttribute), true).FirstOrDefault();

                    if (defaultValueAttribute != null)
                    {
                        object defaultValue = 
                            Convert.ChangeType(defaultValueAttribute.Value, property.PropertyType, CultureInfo.InvariantCulture);

                        propertySetter.Invoke(entity, new object[] { defaultValue });
                    }
                }
            }
        }
    }
}

Tuesday, April 13, 2010

Using RIA Services / WCF with multiple host headers

If you are using RIA Services or just plain old WCF you and you have more than one url that you use to access your website and use IIS 6 you will need to modify your web.config file.

In my example, I access the same IIS web site using two different host headers because I have a small web farm and I want to be able to check each server in the farm after a deployment, not just use the load balanced url.

This means that I have two urls, one for load balancing that everyone uses, and the one I use for testing to make sure a particular server in the farm is working.

In my case, I have http://myapp:8888 which everyone uses, and http://myapp:18888 I use to hit server one, http://myapp:28888 to hit server two, etc.

Here is the change I had to make to my web.config on the first server.

<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" >
<baseAddressPrefixFilters>
<add prefix="http://myapp:8888" />
<add prefix="http://myapp:18888" />
</baseAddressPrefixFilters>
</serviceHostingEnvironment>
</system.serviceModel>

If you don’t do this, you may get errors about anonymous, security, bindings, communication, etc. This tends to fix a lot of these issues.

This is one of the better references I have found for getting RIA Services and WCF running under .Net 3.x, IIS 6, etc.

Friday, March 19, 2010

Getting Started with RIA Services

WCF RIA Services -  You can download a version of RIA Services for either Visual Studio 2010 or 2008. This is also a great page to find sample code, walk-throughs, videos, forums, etc for RIA Services. This is a great starting point.

Below are some links to some more advanced RIA Services topics.

Silverlight 3 and RIA Services - The advanced things – Several interesting advanced topics

Walkthrough: Creating a RIA Services Class Library – If you have a Web Site instead of a Web Application, you may want to look at this since Web Sites can’t be linked to RIA Services. This serves as a nice workaround though and allows for reuse, which is very nice.

How to: Add or Remove a RIA Services Link – Nice if you want to change your RIA Services Link

WCF RIA Services Code Gallery – a good place to get source code for the walk-throughs.

Wednesday, March 10, 2010

RIA Services Rocks!

I cannot believe how cool RIA Services is. I knew it was cool, but I think it does everything I have been dreaming about.

Here is a video that shows how to do a pretty nice application in an hour. Simply AMAZING!!!

http://silverlight.net/learn/videos/all/net-ria-services-intro/

You can download RIA Services for Visual Studio 2008 or VS 2010 Beta. Click here for more info on downloading.

BTW, the entire sample in the video is in the Documentation / Samples of the download.

I’m sold!