pondělí 13. června 2011

Trailing slash in URLs

We are working on an eShop using ASP.Net MVC. The client wanted the URLs to look nice ... no big deal, URLs look nice in MVC, blah.com/detail/15, blah.com/category/45, ...
Not nice enough. We want the name of the article/category in the URL like this: blah.com/15/of_mice_and_men
OK, we are generating all URLs using MVC Futures so there's always some Html.Link("blah", MVC.Detail.Index(Model.ArticleId)) so how can I tweak this to generate that URL?

First thing ... make sure we accept them:

routes.MapRoute(
  "Detail and name",
  "detail/{id}/{name}/",
  new { controller = MVC.Detail.Name, action = MVC.Detail.ActionNames.Index },
  new { id = @"^\d+$" } 
);

next we need to add the name to the RouteDataDictionary somehow. We could add the name as a parameter to the action (even though it doesn't need it) and call MVC.Detail.Index(Model.ArticleId, Model.ArticleName), but we have that on quite a few places already and we do not always have the name handy. Mkay, let's tweak some .tt In this case the T4MVC.tt. And let's do it in a gerenal way ... whenever there is a protected method RenderLink_<ActionName> in the controller, let's call it from the generated pseudoaction in the T4MVC_<ControlerName>Controller in the <ControllerName>Controller.generated.cs and let it tweak the RouteValueDictionary as needed.
Then all that is left is adding


protected void RenderLink_Index(T4MVC_ActionResult callInfo) {
  if (callInfo.RouteValueDictionary.ContainsKey("id") && callInfo.RouteValueDictionary["id"is int) {
   callInfo.AddRouteValue("name"Article.TitleForId((int)callInfo.RouteValueDictionary["id"]).EscapeForNiceUrl());
  }
 }

into the controller and all URLs to details look as requested.

Until they come again that they want a slash at the end of the URL. OK, fine, but how? Including or not the slash in the MapRoute makes no difference. OK, the route object has some GetVirtualPath, is this what I need? I could let the inherited implementation to generate the URL and then append or insert (remember, there may be a query or hash in the URL!) the slash.

Now let's add (copy&paste&tweak) the extension methods to simplify adding the routes


public static class RouteCollectionExtensions {
  public static Route MapRouteWithSlash(this RouteCollection routes, string name, string url) {
   return MapRouteWithSlash(routes, name, url, null /* defaults */, (object)null /* constraints */);
  }
 
  public static Route MapRouteWithSlash(this RouteCollection routes, string name, string url, object defaults) {
   return MapRouteWithSlash(routes, name, url, defaults, (object)null /* constraints */);
  }
 
  public static Route MapRouteWithSlash(this RouteCollection routes, string name, string url, object defaults, object constraints) {
   return MapRouteWithSlash(routes, name, url, defaults, constraints, null /* namespaces */);
  }
 
  public static Route MapRouteWithSlash(this RouteCollection routes, string name, string url, string[] namespaces) {
   return MapRouteWithSlash(routes, name, url, null /* defaults */null /* constraints */, namespaces);
  }
 
  public static Route MapRouteWithSlash(this RouteCollection routes, string name, string url, object defaults, string[] namespaces) {
   return MapRouteWithSlash(routes, name, url, defaults, null /* constraints */, namespaces);
  }
 
  public static Route MapRouteWithSlash(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) {
   if (routes == null) {
    throw new ArgumentNullException("routes");
   }
   if (url == null) {
    throw new ArgumentNullException("url");
   }
 
   Route route = new RouteWithSlash(url, new System.Web.Mvc.MvcRouteHandler()) {
    Defaults = new RouteValueDictionary(defaults),
    Constraints = new RouteValueDictionary(constraints),
    DataTokens = new RouteValueDictionary()
   };
 
   if ((namespaces != null) && (namespaces.Length > 0)) {
    route.DataTokens["Namespaces"] = namespaces;
   }
 
   routes.Add(name, route);
 
   return route;
  }
 
 }

and

routes.MapRouteWithSlash(
  "Detail and name",
  "detail/{id}/{name}/",
  new { controller = MVC.Detail.Name, action = MVC.Detail.ActionNames.Index },
  new { id = @"^\d+$" }
 );


and we have the slash :-)

pondělí 4. dubna 2011

cannot derive from sealed type 'System.ComponentModel.EditorAttribute'

Yep, lovely. Yet another sealed class. Why?!?

OK, so I wrote a Visual Studio plugin that extends the Entity Framework designer. Everything's working fine. Well ... mostly ... the fact that if and only if there is a plugin, any validation problems in the updated .edmx are fatal and attributed to the plugin without the user having any chance to fix the problems ... in 99.784% of cases caused by the original code ... yeah, that's pretty annoying ... the only solution is to write a copy of the .edmx into a different file just before the EF Designer starts the validation and then replace the .edmx by this copy and fix the huge "Hey, I don't know what's the primary key for this view" problem.

Anyway ... most of the attributes I need to add to the properties that I want to display in the EF Designer are kinda OK. [DisplayName("...")], ["Description("...")], [Category("..")], [DefaultValue(...)]. All that's OK.

If I want a dropdown I just define an enum and it works automatically. (And if I want a dropdown with a dynamic list of options it ... well ... I haven't found a way to do that).

Then if I want a multiline input for the property, I know how to do it, it's just an attribute. An attribute that looks like this:

[EditorAttribute(typeof(System.ComponentModel.Design.MultilineStringEditor), 
 typeof(System.Drawing.Design.UITypeEditor))]

Lovely, isn't it?

OK, so I thought I could define subclass (named for example MultiLineAttribute) that'd use the inherited constructor with those values:


public class MultiLineAttribute : System.ComponentModel.EditorAttribute {
  public MultiLineAttribute()
   : base(typeof(System.ComponentModel.Design.MultilineStringEditor),
    typeof(System.Drawing.Design.UITypeEditor)) { }
 }
Nope. Cannot derive from sealed type.

Why?!? Why the heck does the silly class have to be seaeaeaeaealed?

pátek 4. února 2011

LIKE in LINQ to Entities

For whatever reason there is (as far as Google searches suggest) no way to use LIKE in LINQ to Entities.
You can use LIKE in EntitySQL though so ... would you like a like in your LINQ?

No big deal actually.

Add

        <Function Name="String_Like" ReturnType="Edm.Boolean" ef4ex:RenameTo="Like">
          <Parameter Name="searchingIn" Type="Edm.String" />
          <Parameter Name="lookingFor" Type="Edm.String" />
          <DefiningExpression>
            searchingIn LIKE lookingFor
          </DefiningExpression>
          <ef4ex:CodeBlock>
            throw new Exception("Not implemented");
          </ef4ex:CodeBlock>
        </Function>

somewhere into the

<edmx:Runtime><edmx:ConceptualModels><Schema Namespace="Your.Namespace"
...>
and then
[System.Data.Objects.DataClasses.EdmFunction"Your.Namespace""String_Like")]public static Boolean Like(this String searchingIn, String lookingFor) {
 throw new Exception("Not implemented");
}
into a static class (the name doesn't matter) in one the namespaces you tend to be "using" et voila ...

var results = db.EntitySet.Where(e => e.FooBarBaz.Like("%foo%bar%")); 

Big deal, right? (Ignore or remove the ef4ex: attributes and tags, they are used by our customized template so that the C# code above gets generated automatically. If you decide to keep them add xmlns:ef4ex="http://jenda.krynicky.cz/schemas/EF4ex" attribute into the root tag of the .edmx file.)

So what's the catch? There are two ... related. 

First if you use two .edmx files in your project you either have to make sure you are only "using" one of the namespaces in each file (so that the compiler knows which extension method Like() do you mean) or have to rename one of the methods (in the C# code) so that you can access both, but then you have to make sure you always use the right one in each query.

The second is that you can't use this solution somewhere deep within a library that's to be reused with different projects. The Like() is tied fast to the .edmx. If I find a way to overcome this restriction, I'll update this post!

pondělí 10. ledna 2011

C# constructors

As usual, this is yet another in the series of complaints about C#, this time about constructors. Let me state right away that I do believe the syntax of constructors in C# is stupid. It comes as no surprise then that the syntax comes from C++. When it comes to syntax the authors of C and later C++ made quite a few stupid decisions, but I ain't gonna talk about the famous allaroundfix types of C or anything just now. So what do I hate about constructors in C#?
The fact that "A constructor looks very much like a method, but with no return type and a name which is the same as the name of the class". I don't mind the missing return type, what I do mind is the "name which is the same as the name of the class". Why? WHY? WHY?!? Why the fsck would I want to have to repeat the same name over and over again? Especially when constructors, unlike other methods are NOT inherited?

Does

 class BlaBlaBlaBla : BleBleBle {
   public BlaBlaBlaBla( Something one, OrOther two) : base(one, two) {}

ring a bell? This is annoying by itself, but the name of the class repeated as the name of the constructor, makes it about 134.7845% worse. Imagine you need to create several subclasses of a common parent! Instead of copying the first ten or so lines of the first subclass and changing JUST AND ONLY the class name on top, you have to change the name on several places. And then if you happen to add another constructor to the base class and want the subclasses to have it as well ... no, it's not automatic, not even a copy&paste job ... you have to go and change the stupid constructor name in each and every silly subclass.

Thank you very much!

It's funny that if I want to "call" one constructor from another, I do not have to repeat the name

  public BlahBlahBlah(some parameters) : this() {

is enough. WHY? Or rather WHY isn't "this" enough on both places? Why couldn't it be

  public this(some parameters) : this() {

Well it could not. It would be too convenient.

středa 24. listopadu 2010

The specified method 'xxx' on the type 'yyy' cannot be translated into a LINQ to Entities store expression because the instance over which it is invoked is not the ObjectContext over which the query in which it is used is evaluated.

I have a method defined in the partial class for one of the entities like this:

public bool IsAnonymous() {
  return (this.Roles_ & UserRoles.Registered) == 0;
 }



This of course doesn't work within LINQ to Entities queries, because the EF provider for LINQ doesn't know how to translate that to SQL so that it could send that to the database. No problem according to the docs. You can define a "Model Defined Function" and tell the EF to use that:

        <Function Name="IsAnonymous" ReturnType="Edm.Boolean" >
          <Parameter Name="user" Type="Kosmas.Models.User"/>
          <DefiningExpression>
            BitWiseAnd(user.Roles, 1) = 0
         </DefiningExpression>
        </Function>
and

[System.Data.Objects.DataClasses.EdmFunction("Kosmas.Models""IsAnonymous")]
 public bool IsAnonymous() {
  return (this.Roles_ & UserRoles.Registered) == 0;
 }


Or can you?


Well you can't. If you try this you get a very informative error: "The specified method 'Boolean IsAnonymous()' on the type 'Kosmas.Models.User' cannot be translated into a LINQ to Entities store expression because the instance over which it is invoked is not the ObjectContext over which the query in which it is used is evaluated."


A quick Google search did not get anything useful. The only suggestions were to either change the method so that instead of a User instance I call it on the context (making the syntax rather ... silly). Or use Entity SQL (which I'd rather not either).


So I tried whether the example in Programming Entity Framework book actually works and noticed one difference. In the example they were adding the [EdmFunction] attribute to an extension method, while I'm adding it to a plain old ordinary one.


OK, let'ts try that. Let's remove the IsAnonymous method from the partial class and add


public static class Functions {
  [System.Data.Objects.DataClasses.EdmFunction("Kosmas.Models""IsAnonymous")]
  public static bool IsAnonymous(this User obj) {
   return (obj.Roles_ & UserRoles.Registered) == 0;
  }
 }
et voila, it works. I would not call this bug ... if it wasn't. But it is! Anyway, if you do get the nonsensical error message, check whether the method is an ordinary method or an extension one. And see if you can change it from one to the other.

středa 10. listopadu 2010

CSharpCodeProvider.CreateEscapedIdentifier() is it good for anything?

Mkay, so I have an extended T4 template for Entity Framework. One of the things it does is generating enums out of a few marked tables. I mark the table in the Designer and specify the column to use for the enum item names, the values are taken from the primary key. The template then connects to the database, fetches the data from those (static loookup) tables and generates the enums. And I use the code.Escape(name) that the original template uses all over the place to escape the names of the entities and properties. So I am safe right? The options will not be exactly the same as the values in the database, because they have to be valid identifiers, but what the heck. We have intellisense.
Right?

Wrong!
The code.Escape() calls CSharpCodeProvider.CreateEscapedIdentifier() which does ... well nothing really. If the string contains a space, the result will contain a space. If it contains a dash, the result will contain a dash. Etc. etc. etc.
So far it seems the only thing it does is ... if the whole string matches a C# keyword, the method prepends @.

OK, let's see the docs.

Public methodCreateEscapedIdentifierCreates an escaped identifier for the specified value. (Inherited from CodeDomProvider.)
Yeah, sure.
Any other candidates?

Public methodCreateValidIdentifierCreates a valid identifier for the specified value. (Inherited from CodeDomProvider.)
OK, let's try ... nope. Seems the only difference is that instead of @ we get an underscore. But just like the CreateEscapedIdentifier()
code.CreateValidIdentifier("Hello world") == "Hello world"

How's that a valid identifier I really do not know.
Funny thing is that code.IsValidIdentifier("Hello world") returns false. Just like code.IsValidIdentifier(code.CreateValidIdentifier("Hello world")) of course.

Thank you very much Microsoft once again!

pondělí 1. listopadu 2010

Closures and properties -> problems

OK. So C# has lambdas and closures. Fine. You've got to be carefull though! I have not tested all options, but one thing I know for sure already. It's unable to close over an object whose property I access. So if you have something like

foreach (var column in tracked.TrackedColumns) {
 if (column.Type.Contains("char")) {
  column.AsChar = (t => "'\"'+" + (t.Contains("[") ? t + column.Name + "]" : t + ".[" + column.Name + "]") + "+'\"'");
 } else if (column.Type.Contains("date")) {
  column.AsChar = (t => "'\"'" + (t.Contains("[") ? HistoryDatetimeFormat(t + column.Name + "]") : HistoryDatetimeFormat(t + ".[" + column.Name + "]")));
 } else if ...

you will find out that this doesn't work. It'll behave as if all the objects in the collection had the same function, and you end up with the column name of the first one. You have to copy the column name into a local variable. 

foreach (var column in tracked.TrackedColumns) {
 string name = column.Name;
 if (column.Type.Contains("char")) {
  column.AsChar = (t => "'\"'+" + (t.Contains("[") ? t + name + "]" : t + ".[" + name + "]") + "+'\"'");
 } else if (column.Type.Contains("date")) {
  column.AsChar = (t => "'\"'" + (t.Contains("[") ? HistoryDatetimeFormat(t + name + "]") : HistoryDatetimeFormat(t + ".[" + name + "]")));
 } else if