eWorld.UI - Matt Hawley

Ramblings of Matt

Entity Framework Rant

June 10, 2008 08:54 by matthaw

<rant>

Wow, I never thought I'd hate a product so badly just starting to use it. If you've not tried out the new Entity Framework, give it a shot - but be ready to get frustrated and fast. The mere fact that I have to do half of my editing in pure XML and the other half in the designer is absurd. Why doesn't the designer support creating a new entity from my pre-loaded entity set from the database, or the ability to add a new property to my entity! Having to hop over to XML view just to add a new property is painful. I'm also all for "iterations" of code, but why is it when it's generating the model during compile time does it only give me 1 error at a time. Gar! I can't stand this - back to LINQ to SQL.

</rant>



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

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!



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

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



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



2 quick links for your consumption

June 2, 2008 20:13 by matthaw

This is either for your consumption or mine, whatever suits you best!

  1. There's a new technical preview of Windows Live Writer available here.
  2. Use Witty for twitter on the PC, it's f'n awesome!

Ohh, by the way today was the first time that Windows Live Search was defeated in one swift blow from Google when searching for 'twhirl' (Live vs. Google) and 'witty' (Live vs. Google). Just don't keep that up Live, or I'm switching back.



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

I'm Twittering Now... I guess

May 29, 2008 23:04 by matthaw

I'm giving this a try, it's the "fad" thing now - yeah, I'm late to the game, I know. Oh well, let's see how that goes - probably just as good as my MySpace & FaceBook stints... I give it 1.5 months, and it'll be done! Anyway, if your in any way interested, start following me.



Categories: General | Personal
Actions: E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

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



Categories: .NET | ASP.NET | Development | MVC
Actions: E-mail | Permalink | Comments (9) | 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



ASP.NET MVC - Localization Helpers

May 16, 2008 15:58 by matthaw
In addition to blogging, I'm also using Twitter. Follow me @matthawley

 

Note: This post has been updated to work with MVC 2 RTM. The example code is a bit smelly (use POCOs, please!) but the localization helpers do correctly work with the WebFormViewEngine.

 

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:  <% Html.BeginForm("Submit"); %>
   2:    <% if (!string.IsNullOrEmpty((string)ViewData["ErrorMessage"])) { %>
   3:      <div style="color:red;"><%= ViewData["ErrorMessage"] %></div>
   4:    <% } %>
   5:    <%= Html.Resource("Name") %> <%= Html.TextBox("Name", ViewData["Name"]) %><br />
   6:    <%= Html.Resource("Price") %> <%= Html.TextBox("Price", ViewData["Price"]) %><br />
   7:    <%= Html.Resource("Quantity") %> <%= Html.TextBox("Quantity", ViewData["Quantity"]) %><br />
   8:    <input type="submit" value="<%= Html.Resource("Save") %>" />
   9:  <% Html.EndForm(); %>

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, which is only available for the WebFormsView. Should you be using another View Engine, you'll have to modify the codebase to use derived IView type to find the virtual path. So, here's the code:

   1:  public static string Resource(this HtmlHelper htmlhelper, string expression, params object[] args)
   2:  {
   3:    string virtualPath = GetVirtualPath(htmlhelper);
   4:    return GetResourceString(htmlhelper.ViewContext.HttpContext, expression, virtualPath, args);
   5:  }
   6:   
   7:  public static string Resource(this Controller controller, string expression, params object[] args)
   8:  {
   9:    return GetResourceString(controller.HttpContext, expression, "~/", args);
  10:  }
  11:   
  12:  private static string GetResourceString(HttpContextBase httpContext, string expression, string virtualPath, object[] args)
  13:  {
  14:    ExpressionBuilderContext context = new ExpressionBuilderContext(virtualPath);
  15:    ResourceExpressionBuilder builder = new ResourceExpressionBuilder();
  16:   ResourceExpressionFields fields = (ResourceExpressionFields)builder.ParseExpression(expression, typeof(string), context);
  17:   
  18:    if (!string.IsNullOrEmpty(fields.ClassKey))
  19:      return string.Format((string)httpContext.GetGlobalResourceObject(fields.ClassKey, fields.ResourceKey, CultureInfo.CurrentUICulture), args);
  20:   
  21:    return string.Format((string)httpContext.GetLocalResourceObject(virtualPath, fields.ResourceKey, CultureInfo.CurrentUICulture), args);
  22:  }
  23:   
  24:  private static string GetVirtualPath(HtmlHelper htmlhelper)
  25:  {
  26:    WebFormView view = htmlhelper.ViewContext.View as WebFormView;
  27:   
  28:    if (view != null)
  29:      return view.ViewPath;
  30:   
  31:    return null;
  32:  }

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





Copyright © 2000 - 2024 , Excentrics World