Freigeben über


Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 15: ASP.NET MVC

Continuing in our discussion of Silverlight 3 and the update to .NET RIA Services.  I have been updating  the example from my Mix09 talk “building business applications with Silverlight 3”.  I customer recently asked about using ASP.NET MVC and Silverlight with RIA Services.  There specific scenario was an application with the complex admin interface in Silverlight but using ASP.NET MVC for the consumer facing part of the web to get the maximum reach.  The customer wanted to share as much of their application logic as possible. 

So, to address this, i thought I’d update my Mix 09 demo to have an ASP.NET MVC view as well as a silverlight view. 

You can watch the original  video of the full session

You can see the full series here

The demo requires (all 100% free and always free):

  1. VS2008 SP1 (Which includes Sql Express 2008)
  2. Silverlight 3 RTM
  3. .NET RIA Services July '09 Preview
  4. ASP.NET MVC 1.0

Also, download the full demo files and check out the running application.

The architecture we are going to look at today us focused on building the ASP.NET MVC head on the RIA Servces based app logic.  As you will see this is easy to do and shares all the great UI validation support.

image

To start with I took the original application and deleted the MyApp.Web project and added a new project that is ASP.NET MVC based.  You could have just as easily added another web project to the same solution. 

image

Then I associated the Silverlight application with this web project.  Be sure to turn on the RIA Services link as well.  This is what controls the codegen to the Silverlight client.

image

Then i copied over the Authentication related DomainServices from the web project.  

Then i added my northwind.mdf file to App_Data, built my Entity Framework model and finally added my domain service and updated it exactly the way did in Part 2.  The one tweak we need to do is make each of the methods in the DomainService virtual.. this has no real effect on the silverlight client, but it allows up to build a proxy for the MVC client. 

 [EnableClientAccess]
 public class SuperEmployeeDomainService : LinqToEntitiesDomainService<NORTHWNDEntities>
 {
  
     public virtual IQueryable<SuperEmployee> GetSuperEmployees()
     {
         return this.Context.SuperEmployeeSet;
     }
  
     public override void Submit(ChangeSet changeSet)
     {
         base.Submit(changeSet);
     }
  
     public virtual void UpdateSuperEmployee(SuperEmployee currentSuperEmployee)
     {
         this.Context.AttachAsModified(currentSuperEmployee, 
                                       ChangeSet.GetOriginal(currentSuperEmployee));            
     }

Running the MyAppTestPage.aspx should show that we have the same Silverlight app up and running.. 

image

We can now focus on the ASP.NET MVC part of this.

In the Controllers direction, open up HomeContollers.cs.

    1: [HandleError]
    2: public class HomeController : Controller
    3: {
    4:    SuperEmployeeDomainService domainService = 
    5:        DomainServiceProxy.Create<SuperEmployeeDomainService>();

Line 5 calls a factory method that creates a DomainService wrapper.. this allows you to have a clean, direct calling syntax for the DomainService, but allows the system to run through it’s standard pipeline of validation, authorization, etc.    Note, with the current CTP, you will need to reference DomainServiceExtensions.dll from this sample to get this functionality. 

    1: public ActionResult Index()
    2:  {
    3:      return View("Index", domainService.GetSuperEmployees());
    4:  }

Then index is very easy.. we simply pass the results of calling GetSuperEmployee() as our model.  This allows us to share any business logic that filters the results… I can write it once and share it between my Silverlight and ASP.NET MVC app. 

Nothing at all remarkable about the view..  Index.aspx in the \Views directory.

    1: <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
    2:  Inherits="System.Web.Mvc.ViewPage<IQueryable<MyApp.Web.Models.SuperEmployee>>" %>
    3:  
    4: <asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent" runat="server">
    5:     Home Page
    6: </asp:Content>
    7:  
    8: <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    9:    
   10: <h2>List of Employees</h2> 
   11: <ul> 
   12: <% foreach (var emp in Model) { %> 
   13:  
   14: <li>                  
   15:     <%=Html.ActionLink(emp.Name, "Details", new { id = emp.EmployeeID })%>          
   16:     Origin: 
   17:     <%=Html.Encode(emp.Origin)%>    
   18: </li> 
   19: <% } %> 
   20:     
   21: </ul> 
   22:  
   23: </asp:Content>

In line two, we set up the Model type to be IQueryable<SuperEmployee>… notice this is exactly what my DomainService returned.

The in lines 15-18 I get strongly typed access to each SuperEmployee.

Here is how it looks:

image

Next, let’s look at the controller action for the Details view..

    1: public ActionResult Details(int id)
    2: {
    3:     var q = domainService.GetSuperEmployees();
    4:     var emp = q.Where(e=>e.EmployeeID==id).FirstOrDefault();
    5:  
    6:     return View(emp);
    7: }

Again, very simple, here we do a Linq query over the results of calling our business logic.  The cool thing about the composition of Linq is that this where clause passes from here, to the DomainSerivce, to the Entity Framework model and all the way to the database where it is executed as efficient tsql.  

The Details.aspx view is just as simple as the view above, basically just accessing the model.

image

Ok – so the read case is easy, what about update? 

Let’s look at the Edit action in the controller..

    1: public ActionResult Edit(int id)
    2: {
    3:     var q = domainService.GetSuperEmployees();
    4:     var emp = q.Where(e => e.EmployeeID == id).FirstOrDefault();
    5:  
    6:     return View(emp);
    7: }

Again, very similar to the Details action we saw.. this simply populates the fields.

Now let’s take a look at edit.aspx view for this action. We need a simple data entry form..

image

First, we add a Validation summary at the top of the form.  This is where we will display all the validation issues for the page.

    1: <%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %>

Next, we include some standard HTML for each field we need filled out:

    1: <p>
    2:     <label for="Name">Name:</label>
    3:     <%= Html.TextBox("Name", Model.Name) %>
    4:     <%= Html.ValidationMessage("Name", "*") %>
    5: /p>
    6: <p>
    7:     <label for="Name">Gender:</label>
    8:     <%= Html.TextBox("Gender", Model.Gender) %>
    9:     <%= Html.ValidationMessage("Gender", "*")%>
   10: </p>

Notice in line 4 and line 9 we are adding validation hooks…

image

If there are any fields in the type that we don’t want to display, we still need to include them here so they will be in the postback data…

 <p>
 <%= Html.Hidden("EmployeeID", Model.EmployeeID)%>
 </p>

The other thing we want in postback data the set of unedited “original” data.. this is so that we can do concurrency checks with the database.

 <%= Html.Hidden("orginal.EmployeeID", Model.EmployeeID)%>
 <%= Html.Hidden("orginal.Gender", Model.Gender)%>
 <%= Html.Hidden("orginal.Issues", Model.Issues)%>
 <%= Html.Hidden("orginal.LastEdit", Model.LastEdit)%>
 <%= Html.Hidden("orginal.Name", Model.Name)%>
 <%= Html.Hidden("orginal.Origin", Model.Origin)%>
 <%= Html.Hidden("orginal.Publishers", Model.Publishers)%>
 <%= Html.Hidden("orginal.Sites", Model.Sites)%>

Notice here the naming convention of original.blahh.. as you will see later, this matches the name of the argument on the controller action.  Let’s flip back to the controller action and take a look..

    1: [AcceptVerbs(HttpVerbs.Post)]
    2: public ActionResult Edit(SuperEmployee emp, SuperEmployee orginal)
    3: {
    4:  
    5:     if (ModelState.IsValid)
    6:     {
    7:         domainService.AssociateOriginal(emp, orginal);
    8:         domainService.UpdateSuperEmployee(emp);
    9:         return RedirectToAction("Details", new { id = emp.EmployeeID });
   10:     }
   11:    
   12:    return View(emp);
   13:  
   14: }

Notice my method takes two arguments, emp, which is the current employee as it has been updated from the form and the “original” employee that is mapped back from the hidden html fields. 

Then in line 7, we associate the original value with the employee… this effectively makes it so that calls in the DomainService class to ChangeSet.GetOriginal() will return the right value.  Next we call UpdateSuperEmployee() on the business logic.  Here we do all the validation defined for the model including running custom validation code. If validation fails, the error information will be shown on the form.  Otherwise the data is eventually saved to the database.

What I have show is building an ASP.NET MVC application that shares the exact same application logic as my Silverlight client.  This includes things such as data validation that shows right through to the UI.    To bring it home, here is an example of the exact same validation error first in the Silverlight client, then in the ASP.NET MVC client…

image                     

                    image

Comments

  • Anonymous
    July 30, 2009
    Looking forward to tonight's LinkedIn talk. You might ask how many participants are experienced/knowledgeable with RIA services already. Many, I assume, will be just now getting into the details and more interested in the-way-it-is-now, rather than specific deltas from MIX09+.

  • Anonymous
    July 30, 2009
    Thanks!  Yea.. I will focus the talk tonight on just the basics....  

  • Anonymous
    July 30, 2009
    Nice - thanks Brad. I didn't realize this option. Perhaps this should be called just 'Domain Services' and drop the 'RIA'  :) I'm impressed, would like to see more - it's quite powerful.  I especially like the validation aspects. You going to do the 'DTO' option with Domain Services and MVC next?   :)

  • Anonymous
    July 30, 2009
    I should ask one question in terms of architecture. Can I have the following: WebServer - mvc AppServer - domain services ?

  • Anonymous
    July 30, 2009
    Putting the original data on the page and relying on it is probably not a very good idea as it can be easily manipulated.

  • Anonymous
    July 30, 2009
    This is basically how viewstate in webforms works... you could obuscate it pretty easily if that helps. I would not depend on orginal data for any security type decisions..  this is just about optomistic currency.

  • Anonymous
    July 30, 2009
    >I should ask one question in terms of architecture. >Can I have the following: >WebServer - mvc >AppServer - domain services >? Steve -- So the WebServer and the AppServer are different machines?  How do you want to talk between them?  A WCF services?  that should be workable, but we'd need a way to flow the validation information.. we are still working on that. Did I understand you correctly?

  • Anonymous
    July 30, 2009
    A very nice article. I will try to use them in my site

  • Anonymous
    August 01, 2009
    Hi Brad, I was wondering if you could explain how the DomainServiceProxy works.  I would have thought that you would have to call Sumbit() after you called UpdateSuperEmployee() to commit the change.  Or is this done automatically by the proxy?   Typically, the workflow for using a service would be to do some sort of Find or Get. Then run some inserts, updates, deletes and other custom operations as required.  Then call Submit() to commit all the changes and go through the pipeline (Authorize, Validate, Persist, Resolve). In reagrds to the question about having the domain services sit on a different server than the MVC server. Could you not do something like generate a ServiceContext (similar or exactly the same as the one generated for silverlight), then just use this context to make calls from a controller exactly like you would from silverlight.  It would then use HTTP to communicate with the application server.  Or am I way off base here? Great series Brad... Dave

  • Anonymous
    August 02, 2009
    Great tutorial Brad! Thanks a lot. I have been trying to do exactly this for some time now.

  • Anonymous
    August 05, 2009
    Hi. I have installed VS2008 SP1, RIA 3 July09, WPF, etc etc, however even when I try to run the default silverlight application, I get error 2401, not only that I am getting following error msg in VS "Name 'InitializeComponent' is not declared " Please help.

  • Anonymous
    August 06, 2009
    +1 for getting the presentation tier and application tier on separate domains. Many of our customers require us to run in this configuration (with an additional firewall betweent the web tier and application tier). On the validation front I would like to see validation carried out at [at least] three levels the client (browser) (a la xVal), the web tier and the application tier. Minimally, on the web tier and the application tier.

  • Anonymous
    August 25, 2009
    Hi In regards to Steve his post: in our company we have a physical 3-tier application.  mvc front-end web server, mid tier web server (being the application server) and the database server. At the moment we use WCF to talk from front-end to application server. How can we use .NET RIA Services to map to this physical model???