eWorld.UI - Matt Hawley

Ramblings of Matt

Plea for Help: WCF 404 Error

June 6, 2008 20:33 by matthaw

I'm putting out a plea for help. For some odd reason, we have a WCF service (running in IIS 6) that's using streaming and BasicHttpBinding that will return a 404 error message every time it's called from our client. Hitting the same URL on the box itself through IE renders the WSDL just fine. If you have seen this issue, please contact me. We've been struggling with this issue and have yet to find a resolution. BTW, it works great on another web server that is configured the same, and we've looked at all of the verbose logs WCF can give us. Ultimately, we're seeing the message being sent from the client, and a 404 in the IIS log, but nothing on the server logging. Thanks!


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: Development | .NET
Actions: E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

MVC Post-Redirect-Get Sample Updated

June 5, 2008 08:56 by matthaw

I took some time to look back at my MVC Post-Redirect-Get sample and see where it could be improved as well as update it to use the MVC Preview 3 bits. What I found, is again, the core concepts didn't change that much. However, there are some new enhancements that Preview 3 gave us that makes our life a little bit easier. I'll save the full implementation for you to download and checkout yourself, but I do want to highlight some of these enhancements that made the source easier to use.

1. NameValueCollectionExtensions.CopyTo - this made it very nice for me to take all of the posted form data and copy it into the TempData so that upon a redirect, I could extract it out and put it in ViewData.

   1:  if (!BaseValidator.Validate(HttpContext.Request, validators)) {
   2:     NameValueCollectionExtensions.CopyTo(Request.Form, TempData);
   3:     TempData["ErrorMessage"] = BuildErrorMessage(validators);
   4:     return RedirectToAction("Create");
   5:  }

2. I added an extension method for IDictionary to copy between a source and destination, primarily for copying my TempData to ViewData. This way there is no need to do a manual copy of TempData objects all over the place, and is more resilient to changes and additions within your views and controller actions. I'm hoping this makes it into the MVC stack at some point so we all don't have to write this code ourselves.

   1:  public static void CopyTo(this IDictionary<string, object> source, 
   2:                            IDictionary<string, object> destination)
   3:  {
   4:      foreach (KeyValuePair<string, object> pair in source)
   5:      {
   6:          if (!destination.ContainsKey(pair.Key))
   7:              destination.Add(pair.Key, pair.Value);
   8:      }
   9:  }

The above code allows us to simply call the following line of code

   1:  TempData.CopyTo(ViewData);

3. What you'll notice, is that now all of my data is within ViewData, I can start to utilize the built-in functionality added in Preview 3 where the form controls will attempt to extract an initial value from ViewData. This mechanism really brings back the concept of ViewState, except that there is really no overhead to do this! Here's how my form now looks

   1:  <%@ Import Namespace="PRG.Controllers" %>
   2:  <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
   3:  <% using (Html.Form<ProductsController>(c => c.Submit())) { %>
   4:      <h3>Create a New Product</h3>
   5:      Name: <%= Html.TextBox("Name") %><br />
   6:      Price: <%= Html.TextBox("Price") %><br />
   7:      Quantity: <%= Html.TextBox("Quantity") %><br />
   8:      <% if (ViewData["ErrorMessage"] != null) { %>
   9:          <br /><span style="color:red"><%= ViewData["ErrorMessage"] %></span><br />
  10:      <% } %>
  11:      <br />
  12:      <%= Html.SubmitButton() %>
  13:  <% } %>
  14:  </asp:Content>

4. While this next item isn't specifically related to Preview 3, it is a change from my last sample. Previously, I was doing all of my manual validations inline on the server, and it wasn't pretty. As you've probably been reading, I've been making some improvements to the MVC Validation within MvcContrib, and I decided I'd bring in that codebase to this sample. However, to truly show the full PRG pattern, I needed my form to post and alert me that there are errors on the page rather than relying upon client side validation; so I'm simply using the validator objects & server validation. In the next coming weeks, I'll be making another update to MvcContrib to do model based validation. I'll leave this code for your viewing or other examples on my blog.

And that's the updated sample. You can downloaded the latest bits from here. Please let me know what you think and anything else you would do to change this. As each iteration of the MVC framework is released, the sample gets easier and easier! Hope you enjoy this.



kick it on DotNetKicks.com

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Using SubDataItems and View User Controls in ASP.NET MVC

June 4, 2008 08:46 by matthaw

Prior to the preview 3 release of ASP.NET MVC, whenever you wanted to pass data to your view user controls, you only had the option of passing a specific object, or using it's parent's view data. This was all great, and it worked wonderfully, but the problem existed that your view user control would always be dependant upon some parent view data. ASP.NET MVC preview 3 introduced the concept of SubDataItems off of ViewDataDictionary in which you could specify a keyed ViewDataDictionary to use. This helps in the true separation of your view user controls, or child views, and will hopefully later lead into more interesting solutions such as sub-controllers. So lets get into an example.

Hold It! Yeah, we have to patch the MVC framework first before doing anything further. I've logged a bug with the ASP.NET MVC team regarding this, and it's unfortunate to say that this shouldn't have slipped through - but that's what you get without test cases. But I digress. Okay, open the MVC Preview 3 source and open the file "System.Web.Mvc/Mvc/Extensions/UserControlExtensions.cs". Go to the "DoRendering" method, and replace the if block starting on line 127 with the following:

   1:  if (controlData != null) {
   2:      instance.ViewData.Model = controlData;
   3:  }
   4:  else if (!string.IsNullOrEmpty(instance.SubDataKey)) {
   5:      instance.ViewData = context.ViewData.SubDataItems[instance.SubDataKey];
   6:  }
   7:  else {
   8:      instance.ViewData = context.ViewData;
   9:  }

 

Okay, onto the example. Say you have a product detail on your website, and you need to display the product information on the listing page as well as on the order summary page. Because we want to display the same information in the same way on each page, it leads us into using a ViewUserControl. There's other information listed on each page itself, so there is definitely no need to create or duplicate our view data model object just for display purposes. First, we should define our model.

   1:  public class Product
   2:  {
   3:      public Product(int id, string name, decimal price)
   4:      {
   5:          Id = id;
   6:          Name = name;
   7:          Price = price;
   8:      }
   9:   
  10:      public int Id { get; set; }
  11:      public string Name { get; set; }
  12:      public decimal Price { get; set; }
  13:  }

Next, let's work on our controllers. To show the true separation, I'll have both a ProductController and OrderController.

   1:  public class ProductController : Controller
   2:  {
   3:      public ActionResult Index()
   4:      {
   5:          ViewData["Category"] = "Bicycles";
   6:          ViewData["SubCategory"] = "Mountain Bikes";
   7:   
   8:          Product product = new Product(1, "16 Speed", 499.99m);
   9:   
  10:          ViewDataDictionary<Product> subViewData = 
  11:                    new ViewDataDictionary<Product>(product);
  12:          subViewData["ShippingCost"] = 24.99m;
  13:          ViewData.SubDataItems.Add("ProductData", subViewData);
  14:   
  15:          return View();
  16:      }
  17:  }
  18:   
  19:  public class OrderController : Controller
  20:  {
  21:      public ActionResult Index()
  22:      {
  23:          Product product = new Product(1, "16 Speed", 499.99m);
  24:          decimal shipping = 24.99m;
  25:          decimal tax = (product.Price * .08m);
  26:          decimal total = product.Price + shipping + tax;
  27:   
  28:          ViewData["OrderAmount"] = product.Price;
  29:          ViewData["Tax"] = tax;
  30:          ViewData["Total"] = total;
  31:   
  32:          ViewDataDictionary<Product> subViewData = 
  33:                     new ViewDataDictionary<Product>(product);
  34:          subViewData["ShippingCost"] = shipping;
  35:          ViewData.SubDataItems.Add("ProductData", subViewData);
  36:   
  37:          return View();
  38:      }
  39:  }

As you can see, within both of the controller actions, I'm creating a new instance of a ViewDataDictionary<Product> and adding it to the ViewData.SubDataItems. This will allow me to later extract that specific ViewDataDictionary when rendering the ViewUserControl. Granted, this scenario is very rudimentary - but I wanted to show that each action / view has it's own data, but wanted to show the "sharing" of the SubDataItem data. Now, we implement our ViewUserControl (I won't show the HTML for this, it's in the download though). The ViewUserControl simply needs the following

   1:  public partial class ProductInfo 
   2:          : System.Web.Mvc.ViewUserControl<Models.Product> { }

Now, in both our Index views for product & order  we can call the helper method to render our view user control. Please note that within this, we're not specifying any control data (model) but we are specifying the SubDataKey. As you've probably figured out, that SubDataKey is the key the framework uses to extract the correct ViewDataDictionary to set on the ViewUserControl when rendering. Should you not specify a SubDataKey, it'll use the parent ViewDataDictionary - so in this case, our ViewPage's ViewDataDictionary.

   1:  <%= this.RenderUserControl("~/views/shared/ProductInfo.ascx", 
   2:                             null, 
   3:                             new { SubDataKey = "ProductData" }) %>

And that's it. Of course, SubDataItems can be used for other purposes like keeping your ViewData "componentized", but the best use for this so far is so that your ViewUserControl's can live without the knowledge or sharing of parent ViewPage's or ViewUserControl's. If you'd like the source for this demo, you can download it here. It already has the patched MVC assembly. Enjoy!

kick it on DotNetKicks.com


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

RedirectToAction Nasty Bug in ASP.NET MVC Preview 3

June 2, 2008 22:04 by matthaw

We were converting the CodePlex application over to ASP.NET MVC Preview 3 today and found a nasty bug with RedirectToAction. In reality, the bug isn't so much around RedirectToAction, but a change they made internally to Routing. However, this seems to only happen in certain routing scenarios. Take the following routes:

   1:  routes.MapRoute("Login", "site/home/{action}",
   2:     new { controller = "session", action = "login" });
   3:  routes.MapRoute("User Info", "site/user/{action}",
   4:     new { controller = "user", action = "show" });

The problem crops up within your UserController actions when attempting to redirect to other UserController actions. For instance

   1:  public class UserController : Controller {
   2:     public ActionResult Show() { ... }
   3:     public ActionResult Create() {
   4:        return RedirectToAction("Show");
   5:     }
   6:  }

when line 4 is executed above, it attempts to redirect you to "~/site/home/foo". The reason is the change that was made is now trying to remove all the ambiguities by "assuming" things on the fly. Since the actionName overload of RedirectToAction doesn't take and doesn't supply the controller it cannot find the appropriate route. After a bit of convincing, the bug has been acknowledge but I make no guarantees if and when it'll be fixed in a future build - but in the mean time, you're stuck with

  1. Fixing the code yourself from the CodePlex source drop. This simply involves supplying the current executing controller's name in the route value dictionary.
  2. Use the RedirectToAction overload that takes both actionName and controllerName.
  3. Use my lambda expression based RedirectToAction which correctly sends both actionName and controllerName.

And, before anyone asks - why are you creating such complicated routing? Well, simple - simple routing equals a very simple application. Complex routing equals a pre-existing and well defined "RESTful based" urls that mean something when a user reads it.

kick it on DotNetKicks.com


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Lambda Based RedirectToAction Sample Updated to MVC Preview 3

May 29, 2008 18:44 by matthaw

I took my Lambda based RedirectToAction solution I previously blogged about and updated it to work against the Preview 3 bits. I also took the liberty to fix the bug where you couldn't actually call another controller's action. Some notable changes in the source, is that ActionRedirectResult is no more - as it's replaced with RedirectToRouteResult. I think this was a good consolidation between RedirectToAction and RedirectToRoute since they basically do exactly the same thing. You'll also notice that there are 3 more extension methods, which was necessary to fix the prior bug. Now, you can write code like

   1:  // for actions off of the current controller
   2:  return this.RedirectToAction(c => c.Login());
   3:  return this.RedirectToAction(c => c.Login("matt"));
   4:   
   5:  // alternatively, within the UserController you can do the following
   6:  return this.RedirectToAction<ProductController>(c => c.View(102));

I'm not going to post the entire code sample as you can download it here. Enjoy it, and let me know of anything else you'd like to see.

kick it on DotNetKicks.com


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: .NET | ASP.NET | Development | MVC
Actions: E-mail | Permalink | Comments (7) | Comment RSSRSS comment feed

ViewData "dot" Notation Expressions in ASP.NET MVC

May 29, 2008 06:48 by matthaw

Here's something very cool I just found in the ASP.NET MVC Preview 3 bits, you can specify, what I call, "dot" notation expressions on your view data. Say you had the following model:

   1:  public class Bar {
   2:     public int Id { get; set; }
   3:     public string Name { get; set; }
   4:  }
   5:   
   6:  public class Foo {
   7:     public int Id { get; set; }
   8:     public string Name { get; set; }
   9:     public Bar TheBar { get; set; }
  10:  }

When you define your view, you specify Foo as the type of your model. If you cared about type safety prior to run-time, you'd probably write your code in your view like:

<%= Html.Encode(ViewData.Model.TheBar.Name) %>

However, with the new ViewDataDictionary you can now use the "dot" notation expressions to get access the same. As you'll see, you're simply using the indexer of your ViewData object, which internally performs a data-binding eval like operation to search for the key. It is operating in a way that it first will access any specific ViewData items set within your controller, and if that is not found will attempt to perform an eval against your the Model. Using the following controller action:

   1:  public class FooController : Controller {
   2:     public ActionResult View() {
   3:        Foo foo = new Foo() { Id = 1, 
   4:                              Name = "My Foo",
   5:                              TheBar = new Bar() {
   6:                                  Id = 12,
   7:                                  Name = "My Bar"
   8:                              }
   9:                            };
  10:        return View(foo);
  11:     }
  12:  }

You would write the following syntax in your view:

<%= Html.Encode(ViewData["TheBar.Name"]) %>

Given that you have not set the following,

ViewData["TheBar.Name"] = "My Bar";

within your code, you'll be surprised that this works like magic! However, if for any reason you do set the prior ViewData key in the controller action, it's value will be displayed instead of going to your model. Going even further, it doesn't necessarily always have to go off of your Model either, as

ViewData["FooObj"] = foo;

would allow you to access the members with the following expression within your view:

<%= Html.Encode(ViewData["FooObj.TheBar.Name"]) %>

Wow, super-powerful :) You gotta love that syntactical sugar!

kick it on DotNetKicks.com


Currently rated 4.0 by 1 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

ASP.NET MVC - Localization Helpers

May 16, 2008 15:58 by matthaw

You're localizing your application right? Sure, I bet we ALL are - or at least, we're all storing our strings in resource files so that later we can localize. I know, I don't either :) but that doesn't mean if you're working on a large application that needs to be localized in many different languages, you shouldn't be thinking about it. While localization was possible in 1.0/1.1, ASP.NET 2.0 introduced us to a new expression syntax that made localization much easier, simply writing the following code

   1:  <asp:Label Text="<%$ Resources:Strings, MyGlobalResource %>" runat="server" />
   2:  <asp:Label Text="<%$ Resources:MyLocalResource %>" runat="server" />

Of course, you could always use the verbose way and call out to the HttpContext to get local and global resources, but I really enjoy writing the expression syntax much better as it truly implies that the code knows the context of your view / page. So, you could write both of the above examples like

   1:  <%= HttpContext.Current.GetGlobalResourceString("Strings", "MyGlobalResources",
   2:            CultureInfo.CurrentUICulture) %>
   3:  <%= HttpContext.Current.GetLocalResourceString("~/views/products/create.aspx", 
   4:            "MyLocalResource", CultureInfo.CurrentUICulture) %>

So now, you've started on that next big project and have been given the green light to use ASP.NET MVC, but ... your application needs to be localized in Spanish as well. In the current bits, there's really no way of using localized resources aside from (gasp!) using the Literal server control or the verbose method. But, you're moving to MVC to get away from the web forms model & nomenclature, so those are not an option any longer. Well, taking my earlier example of PRG pattern, I decided to "localize" it in an example of your project. First off, you'll need to create your global and local resources. Add a "App_GlobalResources" folder to the root. Add a Strings.resx file, and start to enter your text. Next, we'll add 2 local resources for our views. Under /Views/Products, create a "App_LocalResources", and 2 .resx files named "Create.aspx.resx" and "Confirm.aspx.resx".

 

Okay, now you're all set. Let's start converting our code to use the resources. You'll see that I'm using a new extension method (code will come later) in both the controller actions and in the view itself.

 

   1:  public class ProductsController : Controller
   2:  {
   3:      public ActionResult Create()
   4:      {
   5:          if (TempData["ErrorMessage"] != null)
   6:          {
   7:              ViewData["ErrorMessage"] = TempData["ErrorMessage"];
   8:              ViewData["Name"] = TempData["Name"];
   9:              ViewData["Price"] = TempData["Price"];
  10:              ViewData["Quantity"] = TempData["Quantity"];
  11:          }
  12:          return RenderView();
  13:      }
  14:   
  15:      public ActionResult Submit()
  16:      {
  17:          string error = null;
  18:          string name = Request.Form["Name"];
  19:          if (string.IsNullOrEmpty(name))
  20:              error = this.Resource("Strings, NameIsEmpty");
  21:   
  22:          decimal price;
  23:          if (!decimal.TryParse(Request.Form["Price"], out price))
  24:              error += this.Resource("Strings, PriceIsEmpty");
  25:   
  26:          int quantity;
  27:          if (!int.TryParse(Request.Form["Quantity"], out quantity))
  28:              error += this.Resource("Strings, QuantityIsEmpty");
  29:   
  30:          if (!string.IsNullOrEmpty(error))
  31:          {
  32:              TempData["ErrorMessage"] = error;
  33:              TempData["Name"] = Request.Form["Name"];
  34:              TempData["Price"] = Request.Form["Price"];
  35:              TempData["Quantity"] = Request.Form["Quantity"];
  36:              return RedirectToAction("Create");
  37:          }
  38:   
  39:          return RedirectToAction("Confirm");
  40:      }
  41:   
  42:      public ActionResult Confirm()
  43:      {
  44:          return RenderView();
  45:      }
  46:  }

Next, convert views over to use the new Resource extension method, below is the Create view:

   1:  <% using (Html.Form<ProductsController>(c => c.Submit())) { %>
   2:      <% if (!string.IsNullOrEmpty((string)ViewData["ErrorMessage"])) { %>
   3:          <div style="color:Red;">
   4:              <%= ViewData["ErrorMessage"] %>
   5:          </div>
   6:      <% } %>
   7:      <%= Html.Resource("Name") %> <%= Html.TextBox("Name", ViewData["Name"]) %><br />
   8:      <%= Html.Resource("Price") %> <%= Html.TextBox("Price", ViewData["Price"]) %><br />
   9:      <%= Html.Resource("Quantity") %> <%= Html.TextBox("Quantity", ViewData["Quantity"]) %><br />
  10:      <%= Html.SubmitButton("submitButton", Html.Resource("Save")) %>
  11:  <% } %>

Here's the Confirm view:

   1:  <%= Html.Resource("Thanks") %><br /><br />
   2:  <%= Html.Resource("CreateNew", Html.ActionLink<ProductsController>(c => c.Create(), 
   3:                             Html.Resource("ClickHere"))) %>

As you can see, I'm using a mixture of resource expressions both within the controller and view implementation. Here are the main implementations:

   1:  // default global resource
   2:  Html.Resource("GlobalResource, ResourceName")
   3:   
   4:  // global resource with optional arguments for formatting
   5:  Html.Resource("GlobalResource, ResourceName", "foo", "bar")
   6:   
   7:  // default local resource
   8:  Html.Resource("ResourceName")
   9:   
  10:  // local resource with optional arguments for formatting
  11:  Html.Resource("ResourceName", "foo", "bar")

As you can see, it supports both Global Resources and Local Resources. When working within your controller actions, only Global Resources work as we don't have a concept of a "local resource." The implementation for Html.Resource is actually a wrapper around the verbose method I previously mentioned. It does, however, take into consideration the expression syntax and the context of where the code is calling from to smartly determine the correct resource call to make. A gotcha in the codebase is that this code will only work with the WebFormViewEngine out of the box for local resources. The reason for this is that the code needs a way to find the associated virtual path for the view it's currently rendering using the view engine's own ViewLocator. Should you be using another View Engine, you'll have to modify the codebase to use it's ViewLocator. So, here's the code:

   1:  public static string Resource(this HtmlHelper htmlhelper, 
   2:                                string expression, 
   3:                                params object[] args)
   4:  {
   5:      string virtualPath = GetVirtualPath(htmlhelper);
   6:   
   7:      return GetResourceString(htmlhelper.ViewContext.HttpContext, expression, virtualPath, args);
   8:  }
   9:   
  10:  public static string Resource(this Controller controller, 
  11:                                string expression, 
  12:                                params object[] args)
  13:  {
  14:      return GetResourceString(controller.HttpContext, expression, "~/", args);
  15:  }
  16:   
  17:  private static string GetResourceString(HttpContextBase httpContext, 
  18:                                          string expression, 
  19:                                          string virtualPath, 
  20:                                          object[] args)
  21:  {
  22:      ExpressionBuilderContext context = new ExpressionBuilderContext(virtualPath);
  23:      ResourceExpressionBuilder builder = new ResourceExpressionBuilder();
  24:      ResourceExpressionFields fields = (ResourceExpressionFields)builder
  25:                                              .ParseExpression(expression, typeof(string), context);
  26:   
  27:      if (!string.IsNullOrEmpty(fields.ClassKey))
  28:          return string.Format((string)httpContext.GetGlobalResourceObject(
  29:                                                      fields.ClassKey, 
  30:                                                      fields.ResourceKey, 
  31:                                                      CultureInfo.CurrentUICulture),
  32:                              args);
  33:   
  34:      return string.Format((string)httpContext.GetLocalResourceObject(
  35:                                                      virtualPath, 
  36:                                                      fields.ResourceKey, 
  37:                                                      CultureInfo.CurrentUICulture), 
  38:                              args);
  39:  }
  40:   
  41:  private static string GetVirtualPath(HtmlHelper htmlhelper)
  42:  {
  43:      string virtualPath = null;
  44:      Controller controller = htmlhelper.ViewContext.Controller as Controller;
  45:   
  46:      if (controller != null)
  47:      {
  48:          WebFormViewEngine viewEngine = controller.ViewEngine as WebFormViewEngine;
  49:          if (viewEngine != null)
  50:          {
  51:              virtualPath = viewEngine.ViewLocator.GetViewLocation(
  52:                                                      new RequestContext(controller.HttpContext, 
  53:                                                               controller.RouteData), 
  54:                                                               htmlhelper.ViewContext.ViewName);
  55:          }
  56:      }
  57:   
  58:      return virtualPath;
  59:  }

And just so you know I'm not lying - here's the output in English and Spanish!

English Spanish

Since this example code is so lengthy, I've zipped up the main code to make things much easier for you to bring into your solution.

kick it on DotNetKicks.com


Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

ASP.NET MVC: Expression Based RedirectToAction Method

May 14, 2008 21:29 by matthaw

Update: I've updated the source to be compatible with the Preview 3 bits. Download and execution is the same, but the sample code within the post is no longer valid.

Since the introduction of lambda expressions within the .NET framework, and it's extensive use of them within ASP.NET MVC, I've grown extremely fond of working with compile time errors that lambda expressions gives us. You've seen the ASP.NET MVC team build out a set of ActionLink<T> methods that enable you to specify an expression that will be compiled like the following

<%= Html.ActionLink<UserController>(c => c.Login(), "Login") %>

As the team released their April push and introduced us to ActionResult return types, I've totally fell in love with using these, mainly from a testability standpoint, and it also makes your code extremely readable to determine exactly what is going to be happening at what point. However, what I've found is that when using RedirectToAction, I would constantly be writing code like

   1:  public class UserController : Controller
   2:  {
   3:     public ActionResult Login() { ... }
   4:     public ActionResult ProcessLogin()
   5:     {
   6:        // ... determine if error'd
   7:        return RedirectToAction("Login");
   8:     }
   9:  }

Yup, nothing fancy and pretty straightforward. However, what if I needed to refactor my codebase and change my action method names... the task becomes straight forward when using ActionLink<T> in my views, but all of my controllers continue to compile even when they shouldn't be! Yes, if you're doing true TDD, this task is easy to spot because after refactoring, you can run all of your test cases and see which ones failed.

 

Great. That's a lot of manual work...mmmmkay. In the age of having refactoring shortcuts built right into the IDE, why couldn't I change my method name using refactoring, and have IT do all the work for me? Enter, the expression based RedirectToAction method. I know, you saw it coming, right? Here's how I want to write my above code

   1:  public class UserController : Controller
   2:  {
   3:     public ActionResult Login() { ... }
   4:     public ActionResult ProcessLogin()
   5:     {
   6:        // ... determine if error'd
   7:        return this.RedirectToAction(c => c.Login());
   8:     }
   9:  }

Ohh, pretty - and hey look, refactoring now works! It even supports parameters, route value dictionaries, anonymous types. Even better, you can specify a completely different controller to route to!

   1:  // parameters, route dictionaries, anonmyous types
   2:  this.RedirectToAction(c => c.Login("matt"));
   3:  this.RedirectToAction(c => c.Login(), new RouteValueDictionary(new { userName = "matt" }));
   4:  this.RedirectToAction(c => c.Login(), new { userName = "matt" }));
   5:   
   6:  // different controller
   7:  this.RedirectToAction<ProductsController>(c => c.View(101));

All this is super powerful, and I'm sure your dying to get your hands on the source...okay :)

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq.Expressions;
   4:  using System.Web.Mvc;
   5:  using System.Web.Routing;
   6:   
   7:  public static class ControllerExtensions
   8:  {
   9:      public static ActionRedirectResult RedirectToAction<T>(this T controller, 
  10:          Expression<Action<T>> action) where T : Controller
  11:      {
  12:          return RedirectToAction<T>(controller, action, null);
  13:      }
  14:   
  15:      public static ActionRedirectResult RedirectToAction<T>(this T controller, 
  16:          Expression<Action<T>> action, object values) where T : Controller
  17:      {
  18:          return RedirectToAction<T>(controller, action, new RouteValueDictionary(values));
  19:      }
  20:   
  21:      public static ActionRedirectResult RedirectToAction<T>(this T controller, 
  22:          Expression<Action<T>> action, RouteValueDictionary values) where T : Controller
  23:      {
  24:          MethodCallExpression body = action.Body as MethodCallExpression;
  25:          if (body == null)
  26:              throw new InvalidOperationException("Expression must be a method call.");
  27:   
  28:          if (body.Object != action.Parameters[0])
  29:              throw new InvalidOperationException("Method call must target lambda argument.");
  30:   
  31:          string actionName = body.Method.Name;
  32:          string controllerName = typeof(T).Name;
  33:   
  34:          if (controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
  35:              controllerName = controllerName.Remove(controllerName.Length - 10, 10);
  36:   
  37:          RouteValueDictionary parameters = LinkBuilder.BuildParameterValuesFromExpression(body);
  38:   
  39:          values = values ?? new RouteValueDictionary();
  40:          values.Add("controller", controllerName);
  41:          values.Add("action", actionName);
  42:   
  43:          if (parameters != null)
  44:          {
  45:              foreach (KeyValuePair<string, object> parameter in parameters)
  46:              {
  47:                  values.Add(parameter.Key, parameter.Value);
  48:              }
  49:          }
  50:   
  51:          return new ActionRedirectResult(values);
  52:      }
  53:  }

As you can see, it's fairly straight forward. This is something that I think is very useful, and gets more in line with how code should be written. Since this example is a bit lengthy, I've provided the source in downloadable format, so click here to get it. Hope you enjoy and use this in your applications!

kick it on DotNetKicks.com

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

ASP.NET MVC: UI Validation Framework Update

May 14, 2008 08:53 by matthaw

I've taken some recommendations regarding the MVC UI Validation framework I originally posted about, which is available within the MvcContrib project. I'm still working on future advancements like attributing your model, but I did make a significant step forward ensuring both client and server side validation is successful. With some simple changes to how you are generating your validators, you can easily achieve this new functionality. Let's use an example:

   1:  using MvcContrib.UI.Tags.Validators;
   2:   
   3:  public class UserController : Controller
   4:  {
   5:     public ActionResult Index()
   6:     {
   7:        return RenderView();
   8:     }
   9:   
  10:     public ActionResult Add()
  11:     {
  12:        return RenderView(new UserData());
  13:     }
  14:   
  15:     public ActionResult Save()
  16:     {
  17:        UserData data = new UserData();
  18:        BindingHelperExtensions.UpdateFrom(data, Request.Form);
  19:   
  20:        if (!BaseValidator.Validate(Request.Form, data.Validators))
  21:           return RenderView("Add", data); // You should follow PRG pattern
  22:   
  23:        // save the data
  24:   
  25:        return RedirectToAction("Index");
  26:     }
  27:  }
  28:   
  29:  public class ValidatorViewData
  30:  {
  31:     private IList<IValidator> validators = new List<IValidator>();
  32:   
  33:     public IList<IValidator> Validators
  34:     {
  35:        get { return validators; }
  36:     }
  37:  }
  38:   
  39:  public class UserData : ValidatorViewData
  40:  {
  41:     public UserData()
  42:     {
  43:        this.Validators.Add(new RequiredValidator("firstNameRequired", "firstName", 
  44:                "First Name is Required"));
  45:        this.Validators.Add(new RegularExpressionValidator("firstNameRegex", "firstName", 
  46:                "[a-zA-Z]*", "First Name can only contain letters."));
  47:        this.Validators.Add(new RequiredValidator("ageRequired", "age", "Age is required."));
  48:        this.Validators.Add(new RegularExpressionValidator("ageRegex", "age", "[0-9]*", 
  49:                "Age can only be numeric."));
  50:        this.Validators.Add(new RangeValidator("ageRange", "age", "18", "35", 
  51:                ValidationDataType.Integer, "Age is not in the range of 18-35."));
  52:     }
  53:   
  54:     public string FirstName { get; set; }
  55:     public string Age { get; set; }
  56:  }

In this controller and view data, we see that we're generating our validators within the view data object. This enables us to validate the Request.Form collection against our list of validators as seen in line 20 above. This works because when we created each of our validators, we gave it the name of the form element we're expecting the value to be present in. When taking it to our view implementation, we'll use a new method to register our validators:

   1:  <%@ Import Namespace="System.Web.Mvc" %>
   2:  <%@ Import Namespace="MvcContrib.UI.Html" %>
   3:  <html>
   4:     <head>
   5:        <%= Html.Validation().ValidatorRegistrationScripts() %>
   6:     </head>
   7:     <body>
   8:        <% using (Html.Form<UserController>(c => c.Save(), Formmethods.Post, 
   9:              Html.Validation().FormValidation())) { %>
  10:        First Name: <%= Html.TextBox("firstName", this.ViewData.FirstName) %>
  11:        <%= Html.Validation().ElementValidation(this.ViewData.Validators, "firstName");
  12:        <br />
  13:        Age: <%= Html.TextBox("age", this.ViewData.Age) %>
  14:        <%= Html.Validation().ElementValidation(this.ViewData.Validators, "age");
  15:        <br />
  16:        <%= Html.SubmitButton("submit", "Save") %>
  17:        <% } %>
  18:     </body>
  19:  </html>

What you'll notice from the original is that we now have an ElementValidation extension method. This enables you to specify the list of validators you would like to be rendered at this time. You have the option of specifying the field name to filter the list by when rendering as well. Should any validators fail upon the server, and you re-render the view, those validators will initially be displayed. All in all, the approach is getting closer to what it needs to be. I'll be working further on the following features to really bring this to be a first class citizen:

  1. Move the Validate method from BaseValidator into a Controller extension method.
  2. Define a set of attributes that you can apply to your model's properties that will work with this validation model.

A few things to note, is that you can still explicitly use individual validators, but note that they're no longer off of Html.Form() but Html.Validation() now. Lastly, while these changes have been committed to the MvcContrib trunk, they're still not available in any publicly released builds. You'll have to download the source and compile the binaries yourself until MvcContrib does another official build. Let me know what you think, and yes - I know, some people still don't like this approach :) It's not meant for everyone, and there's other projects out there that you can use. Enjoy, and let me know what you think of the progress.

kick it on DotNetKicks.com


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

ASP.NET MVC - Extracting Web Resources

May 13, 2008 11:40 by matthaw

One nice feature that ASP.NET added in the 2.0 feature set was the ability to embed resources directly within an assembly and then later extract them through a separate Http Handler. You've notably saw this by all the WebResource.axd calls. If you're building a ASP.NET MVC view, you have two ways of extracting web resource urls.

Way 1

If you are using the default view engine, all of your views should derive either from ViewPage or ViewUserControl, which ultimately has access to ClientScript.WebResourceUrl, etc. This method is the easiest to use as it doesn't require anything special:

<script src="<%= ClientScript.WebResourceUrl(typeof(Foo), "Foo.Bar.js") %>" type="text/javascript">
</script>

This way works great, as I said if you're using the WebFormsViewEngine and you're within the view itself. However sometimes you have a controller action or other html helper extension methods that are rendering HTML directly ... or you're just using a completely different view engine. At this point, you don't have access to ClientScript.

Way 2

The second way is by having your own extension method that will generate the web resource url for you. This way, you can get a web resource url anywhere you have an instance of your HtmlHelper object.

   1:  public static class HtmlHelperExtensions
   2:  {
   3:     private static MethodInfo getWebResourceUrlMethod;
   4:     private static object getWebResourceUrlLock = new object();
   5:   
   6:     public static string WebResourceUrl<T>(this HtmlHelper htmlHelper, string resourceName)
   7:     {
   8:        if (string.IsNullOrEmpty(resourceName)
   9:           throw new ArgumentNullException("resourceName");
  10:   
  11:        if (getWebResourceUrlMethod == null)
  12:        {
  13:           lock (getWebResourceUrlLock)
  14:           {
  15:              if (getWebResourceUrlMethod == null)
  16:              {
  17:                 getWebResourceUrlMethod = typeof(AssemblyResourceLoader).GetMethod(
  18:                                                  "GetWebResourceUrlInternal",
  19:                                                  BindingFlags.NonPublic | BindingFlags.Static);
  20:              }
  21:           }
  22:        }
  23:   
  24:        return "/" + (string) getWebResourceUrlMethod.Invoke(null,
  25:                         new object[] { Assembly.GetAssembly(typeof(T)), resourceName, false });
  26:     }
  27:  }

And that's it. Now, in your code you can get a web resource url very similarly as with ClientScript:

<script src="<%= Html.WebResourceUrl<Foo>("Foo.Bar.js") %>" type="text/javascript">
</script>

What I don't like about this approach is that it is using reflection to invoke the private method, but considering these methods are not exposed anywhere (ohh, and you cannot create an instance of ClientScriptManager) this is the only approach that can be taken. Furthermore, remember this is an ASP.NET feature, and works very nicely within the MVC framework. The url, WebResource.axd, is mapped to the AssemblyResourceLoader Http Handler which is independent of the ASP.NET web forms runtime. Hope this helps some people!

kick it on DotNetKicks.com


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: .NET | ASP.NET | Development | MVC
Actions: E-mail | Permalink | Comments (7) | Comment RSSRSS comment feed


Copyright © 2000 - 2008 , Excentrics World