Archive

Archive for the ‘MVC’ Category

MVC 4 Project Templates are stupid

February 19th, 2012 No comments

<begin rant>

Thought I would jump on the MVC 4 Beta bandwagon today, installed, create new project… And this is what I’m greeted with:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="AspNetMvc" version="4.0.20126.16343" />
  <package id="AspNetRazor.Core" version="2.0.20126.16343" />
  <package id="AspNetWebApi" version="4.0.20126.16343" />
  <package id="AspNetWebApi.Core" version="4.0.20126.16343" />
  <package id="AspNetWebPages.Core" version="2.0.20126.16343" />
  <package id="EntityFramework" version="4.1.10331.0" />
  <package id="jQuery" version="1.6.2" />
  <package id="jQuery.Ajax.Unobtrusive" version="2.0.20126.16343" />
  <package id="jQuery.UI.Combined" version="1.8.11" />
  <package id="jQuery.Validation" version="1.8.1" />
  <package id="jQuery.Validation.Unobtrusive" version="2.0.20126.16343" />
  <package id="knockoutjs" version="2.0.0.0" />
  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" />
  <package id="Microsoft.Web.Optimization" version="1.0.0-beta" />
  <package id="Modernizr" version="2.0.6" />
  <package id="System.Json" version="4.0.20126.16343" />
  <package id="System.Net.Http" version="2.0.20126.16343" />
  <package id="System.Net.Http.Formatting" version="4.0.20126.16343" />
  <package id="System.Web.Http.Common" version="4.0.20126.16343" />
  <package id="System.Web.Providers" version="1.1" />
  <package id="System.Web.Providers.Core" version="1.0" />
</packages>

This is absolutely stupid…

I select EMPTY project template.

EMPTY

So I removed the things I don’t need to begin with:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="AspNetMvc" version="4.0.20126.16343" />
  <package id="AspNetRazor.Core" version="2.0.20126.16343" />
  <package id="AspNetWebApi" version="4.0.20126.16343" />
  <package id="AspNetWebApi.Core" version="4.0.20126.16343" />
  <package id="AspNetWebPages.Core" version="2.0.20126.16343" />
  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" />
  <package id="Microsoft.Web.Optimization" version="1.0.0-beta" />
  <package id="System.Json" version="4.0.20126.16343" />
  <package id="System.Net.Http" version="2.0.20126.16343" />
  <package id="System.Net.Http.Formatting" version="4.0.20126.16343" />
  <package id="System.Web.Http.Common" version="4.0.20126.16343" />
  <package id="System.Web.Providers" version="1.1" />
  <package id="System.Web.Providers.Core" version="1.0" />
</packages>

But beyond that I don’t know if the rest can be removed or if it’s required by MVC 4. Time for some trial and error Sad smile

I really hope MS decide to create a REAL empty project.

Categories: MVC, Rant Tags:

Post a collection of ViewModel’s to a MVC Action with jQuery

December 7th, 2011 No comments

Maybe I searched for the wrong thing, but I couldn’t find what I was looking for Sad smile My Bing and Google fu failed me.

Basically I wanted to post a collection of ViewModels to an MCV action. Turns out it’s rather simple.

Lets say I have a bunch of Products, and Products are managed in a WarehouseLocation. A product doesn’t have a warehouse location, since it could exist in multiple locations.

If I’m currently working in Location A, I want to post a collection of Products to an action, as well as the WarehouseLocationId.

So given a simple ViewModel, and an Action:

public class ProductViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

and

public JsonResult Update(int warehouseLocationId, IEnumerable<ProductViewModel> products)
{
    //Do something with the products…

    return Json(new {Staus = "success"});
}

I think usually when someone sends data from jQuery it’s usually a single parameter,so the JSON would look something like:

var data = { id: 1,name: ‘test name’, price: 15.95 };

This would populate an action that looked like:

public JsonResult Update(ProductViewModel product)

But what I was faced with was passing in two parameters, one of which is a collection…

MVC seems to pair up the posted result with the parameter name, in the same way they it does for Route parameters. So to the JSON needs to look like:

var data = {
    warehouseLocationId: 12,
    products: [{
        Id: 1,
        Name: "Product 1",
        Price: 15.95
    }, {
        Id: 3,
        Name: "Product 2",
        Price: 12.50
    }]
};

As you can see the key’s on the first level match the parameter names, while the array on products, match the ViewModel.

Taking this exact data and posting it to the action:

$.ajax({
        type: 'POST',
        url: '@Url.Action("Update", "Home")',
        data: JSON.stringify(data),
        contentType: 'application/json',
        success: function(result) {
            //Do something with result…
        },
        dataType: 'json'
    });

 

I submit that, and with a breakpoint on my action I see:

image

^ The warehouseLocationId…

image

^ 2 products with the values:

image

And:

image

The exact same data we defined in our JavaScript.

The really cool thing about this, is you can have nested collections also. Using the same scenario, but extending Product to have a collection of Categories like so:

public class ProductViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    public IEnumerable<Category> Categories { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

I can update the JSON object to include Category information on 1 of the two products:

var data = {
    warehouseLocationId: 12,
    products: [{
        Id: 1,
        Name: "Product 1",
        Price: 15.95
    }, {
        Id: 3,
        Name: "Product 2",
        Price: 12.50,
        Categories: [{
            Id: 1,
            Name: "Category 1"
        }, {
            Id: 1,
            Name: "Category 2"
        }]
    }]
};

And submit that in the same way as before, capturing the results in the Action we get the first product with null for the categories, since we didn’t define it as an empty array:

image

While the second Product has 2 items, the first item is Category 1, and the second item is Category 2.

image

image

And that’s it, easy peasy, sending a collection of ViewModels from jQuery to an MVC Action.

I <3 MVC Smile

Categories: MVC, jQuery Tags: , ,

Optional Parameters with AttributeRouting

November 27th, 2011 3 comments

I found a little trick with using Optional Parameters with AttributeRouting, by using standard optional parameters in the action.

The documentation says you can add an attributes to specify the defaults, or add ‘=value’ to the parameter name, and I guess that’s a more correct way to generate routes, but you can achieve the same affect by making the parameter optional. Like so:

[GET("videos/{?page}")]
public ActionResult Videos(int page = 1)
{
    return View("Result");
}

If I browse to the URL:

image

It uses the default value of 1.

image

Now when appending a number to the end of the URL:

image

It captures the correct value:

image

I prefer this method, the optional parameter on the action is more quickly identified, than looking at the attribute to see the defaults. (in my opinion)

Ahh AttributeRouting, how I love you.

Website Folder Structure? CSS Files? Does anyone care? I do…

October 17th, 2011 2 comments

When we build website’s, more often than not we have: Separation of Concerns. Even if at most it’s just basic 3-tier Architecture

What annoys me though is a lot of time no one ever thinks about separation of concerns when it comes to the folder structure of their website, and where things go.

Folders

The one thing that annoys me above all else, is when ‘layout images’ (images for layout, design, styling etc) of the website, are shoved into the same folder that holds what I call “Content Images”.

For example:

root/
root/css/
root/images/

What I prefer to do is structure it so the css folder has it’s own images folder, specifically for layout images.

root/
root/css/
root/css/images/
root/images

To take it a step further, I often have images, icons, and fonts.

root/
root/css/
root/css/fonts/
root/css/icons/
root/css/images/
root/images

This way I don’t have to worry about finding specific icons in a folder full of images, I don’t have to worry about sifting through images to find ones related to the layout/design, they are all neatly tucked away in their own specific areas.

When it comes to folder structure however, NuGet gets in the way.

It would be nice if it allowed you to specify where file types should go. At the moment ‘css’ files seem to go into ‘Content’. JavaScript files go into ‘Scripts’. But what I would rather have is all lower-case names, and ‘css’ rather than ‘content’. Until then, I just download those sort of dependencies manually and create my own folder structure.

CSS

The next thing that I find annoying is the God Style Sheet. It’s the StyleSheet that people name:

Styles.css

It’s the only one that exists in the project, it’s over 100k in size, no comments, no christmas trees. Just lines and lines of fail.

The approach I used to take (before finding .LESS) was creating multiple StyleSheets that were small and easy to maintain, sometimes I would end up with up to 12, possibly more depending on the size of the website.

Each StyleSheet would be responsible for a specific task, so I would have something like the following:

/css/reset.css (this is just one of those many reset StyleSheets found on the net)
/css/layout.css
/css/main.css
/css/header.css
/css/footer.css
/css/main-navigation.css
/css/…

When sections of the website were broken down like this, it did require flicking between StyleSheets now-n-then, but they became much smaller and easier to maintain. I also became far-less at risk of Unwanted Side Effects.

Now with .LESS and LESS, it’s easy to have only a few StyleSheets, since now you can tab within a specific region and continue to write styles that only apply to that region.

For example, given a Footer element. You could come up with something like:

footer {
    border-top: 2px #182a33 solid;
    padding-top: 30px;
    nav {
        a {
            font-family: @footer-link-font;
            font-size: @footer-link-size;
            color: @footer-link-color;
            text-decoration: none;
            &:hover {
                color: @footer-link-color-hover;
                text-decoration: underline;
            }    
        }
        li > span {
            color: @footer-text-color;
            font-family: @footer-text-font;
            font-size: @footer-text-size;
        }
        h4 {
            color: @footer-heading-color;
            font-family: @footer-heading-font;
            font-size: @footer-heading-size;
            font-weight: bold;
            margin-bottom: 35px;
            padding-top: 17px;
        }
        > ul > li {
            width: 165px;
            float: left;
            display: block;
            line-height: 18px;
        }
    }
}

This would in turn generate the following CSS for you.

footer {
  border-top: 2px #182a33 solid;
  padding-top: 30px;
}
footer nav a {
  font-family: arial;
  font-size: 12px;
  color: #00adee;
  text-decoration: none;
}
footer nav a:hover {
  color: #00adee;
  text-decoration: underline;
}
footer nav li > span {
  color: #a8a8a8;
  font-family: arial;
  font-size: 12px;
}
footer nav h4 {
  color: white;
  font-family: arial;
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 35px;
  padding-top: 17px;
}
footer nav > ul > li {
  width: 165px;
  float: left;
  display: block;
  line-height: 18px;
}

I find it much easier to manage writing ‘LESS’ then traditional CSS.

Conclusion

I didn’t want to go into too much detail, my main point is I think people should put a little more thought into how they structure their folders, files, naming, etc when creating websites. We focus so much time on trying to main our applications and code maintainable, but somehow neglect the website itself, and after a while we just end up fighting with it.

MVC Routing with Attributes, makes Routing Awesome

August 9th, 2011 3 comments

I evaluated HEAPS of code/libraries and threw in the towel and decided that getting lower-case routes, while still having Area’s, was a complete and utter waste of time and effort because they were all crappy, and broke stuff, or didn’t work.

That was until my mate Brad grabbed ‘AttributeRouting’ from Nuget. Oh well you know what, this is the best thing sliced bread.

First and foremost, it solved the number 1 issue I had with routing, lowercase Urls. This is how simple is it to make Routes lowercase.

routes.MapAttributeRoutes(config =>
{
    config.UseLowercaseRoutes = true;
});

Wow… Yet you bing for lowercase routes, and you end up with strange solutions… Here’s a couple.

http://stackoverflow.com/questions/878578/how-can-i-have-lowercase-routes-in-asp-net-mvc

This question links to:

http://coderjournal.com/2008/03/force-mvc-route-url-lowercase/

This breaks Area’s, why? Because to handle an Area it appends ‘?Area=areaname’ to the Url. FAIL. Probably worked great for MVC 1,but considering I’ve seen it linked in MVC 3 questions,I consider it fail.

http://stackoverflow.com/questions/878578/how-can-i-have-lowercase-routes-in-asp-net-mvc/1731652#1731652

Writing them in lowercase? Really?

Pass, I’ll stick with AR, it’s too simple, it’s ridiculously simple. I love it!

The next thing I love, is that instead of writing Routes, and then writing ‘HttpGet’ ‘HttpPost’ on all the actions, I just have to write ‘GET’ and ‘POST’ on the actions, and even define the URL along with it, and bam, it’s clear and explicit how you get to that action.

Such as:

[POST("Auth/Login")]
public ActionResult Login(LoginViewModel loginViewModel, string returnUrl)

It’s obvious to me, that I can access this Action by navigating to www.mysite.com/auth/login

[GET("Content/{slug}")]
public ActionResult Index(string slug)

Page content can be accessed via www.mysite.com/content/my-slug

And when controllers are divided into Area’s, I can just define the area with the controller class:

[RouteArea("Admin")]
public class HomeController : BaseController

I could define the namespace to use for Area’s in the Area Registration, but it just seems… hacky… AR is definitely worth checking out.

MVC +Areas + Routes… Order of Routes Matter!

July 27th, 2011 3 comments

The order in which routes are registered, really is pretty damn important, otherwise it can have really strange side-effects.

I have a site, which has 3 areas, and no default… ‘non-area’.

  • Admin
  • Members
  • Site
    So rather than having the structure:

image

Where the root/default site is in the main directory, with two areas. I wanted this structure:

image

So no root/default, and just a ‘Site’ area which would be the default.

(Note: The default route is removed from Global.asax… for now)

This works perfectly fine if all the URL’s are accessed like:

http://localhost:147/Site/Home/Index

http://localhost:147/Admin/Home/Index

http://localhost:147/Member/Home/Index

If we run this in a browser we get the following:

image

These routes are registered automatically when you add an Area, in a *AreaName*AreaRegistration file.

image

The routes generated look like the following:

namespace RoutingIssue.Areas.Members
{
    public class MembersAreaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get
            {
                return "Members";
            }
        }

        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
                "Members_default",
                "Members/{controller}/{action}/{id}",
                new { action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}

But with different AreaName’s of course. You get the idea.

Now what we want to do is change the Site route, to work from the root directly like:

http://localhost:147/

This is where my assumptions began to go wrong… I updated the route like so:

public override void RegisterArea(AreaRegistrationContext context)
{
    context.MapRoute(
        "Site_default",
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

So I’ve removed ‘Site’ from the path, and added the default controller.

This is where things started to get messy. At first, it worked, in my sample project. Then I added AutoFac, MiniProfiler, some Content, and a few other things. That’s when it all went pear shaped.

image

It seems to have lost the information about the routes for Admin/Member.

Creating a new project with it working, and with my current project. I wrote some
Trace.Write(“AreaName”) code into each AreaRegistration. The working project outputted:

Members
Admin
Site

However the second project, where it was failing, outputted:

Site
Members
Admin

So what’s happening is in my project, it registers the default route first without the Area:

context.MapRoute(
    "Site_default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Took me hours to figure this out. That MVC cannot guarantee the order of which Area’s are registered. The second two Area’s didn’t stand a chance.

The solution was to move the Site route to the global.asax file, and specify an Area on it. My Site registration looks like:

public override void RegisterArea(AreaRegistrationContext context)
{
    //context.MapRoute(
    //    "Site_default",
    //    "{controller}/{action}/{id}",
    //    new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    //);
    Trace.WriteLine("Site");
}

While my global.asax file has:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "default",
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional },
        new string[] { "RoutingIssue.Areas.Site.Controllers" }
    ).DataTokens.Add("Area", "Site");
}

This means the default route is registered after my Areas, and everything works perfectly again Smile

Categories: MVC Tags: , ,