Converting ASP.NET WebForms to ASP.NET MVC 4.0

Monday, April 22, 2013 / Posted by Luke Puplett / comments (2)

This is a blog-in-progress while I try to convert an ASP.NET WebForms application to MVC 4. It may completely fail or I may give up, but I thought it might help to share my experiences.

What I am Migrating

It’s a one project ASP.NET WebForms 3.5 site. It’s pretty simple, uses the old Ext JavaScript framework, which became Sencha UI, I think. There’s a fair few pages but not a lot of HTML in each, since its built in XSLT (vomit) from XML coming from an XML database. Much business logic is in the data-layer (vomit II).

Strategy

My bright idea is not to convert. I don’t think that’s the easiest route, I just don’t know what’s needed for an MVC app, and I want the IDE to be in MVC mode, with the context menu support for views and stuff, which probably won’t happen if I just add some DLL references and setup some routing.

So, I will make a new, empty MVC 4 app and copy in the files from the old world. I know MVC is happy to serve-up ASPX forms pages and controls, and that’s all a WebForms site is – just some ASPX pages and some handlers, maybe some URL rewriting.

Start

So far, I have:

  • Created an empty, new ASP.NET MVC 4.0 project.
  • Set the same project references and NuGet packages.
  • Set my solution and project configurations for staging/QA.
  • Copied over all the stuff from the old Web.config that I think are non-standard, i.e. added to support the old app. I already did this, so its hard to blog at detail but its actually pretty simple.
  • Begun to copy over the basic, high-in-the-dependency-graph controls and pages.

Copying Stuff Across

I have copied /MasterPages and its children, /Classes which are just some .cs files with helpers inside, /Controls which are Web User Controls or ASCX files as well as the default.aspx (all come with their code-behind and designer).

Problem 1 – Solved

In copying the files, drag and drop, from the WebForms project in the same solution, the IDs of the controls on the ‘pages’ (in the ASPX or ASCX files) are not being ‘seen’ in the code-behind. By that, I mean there are red squigglies in the C# wherever they are referenced – its like the controls on the pages are not being compiled.

I reconstructed a control manually, by adding a new one with a different name and copying over the important mark-up and code. This was fine, so MVC is cool with it, just doesn’t like it being copied file by file.

So I figured that it must be related to the designer file. The file doesn’t sit at the same level in the Solution Explorer as the manually created good one, so there’s something odd going on. Opening the designer.cs file is fine but the code doesn’t respond to mouse-overs – its lifeless like a text file.

Solution: The trick is to delete the file and then right-click its parent AS?X file and hit Convert to Web Application which forces regeneration of the designer.cs.

You can copy a load in and then convert at the folder or project level, too, donchaknow.

Problem 2 – Solved

The default route and getting default.aspx to be the page shown at the domain root. This one is easy, although I’m not sure its the proper way. Simple add this route.

routes.MapPageRoute("HomePage", "", "~/default.aspx");

Problem 3 – Solved

Settings in httpHandlers not working, i.e. still going via the routing system. So this site has a load of magic setup in the web.config to make friendly-URLs happen. Of course, this needs to be re-considered in an MVC world, but we’re talking about things like blah.xml which invokes a special handler – its all custom stuff for this old site.

The solution was two step:

- Add the following line to not route requests:

routes.IgnoreRoute("{resource}.xml");

- Also need to update the types in the httpHandlers section in web.config

<add verb="*" path="*.xml" type="Company.XmlHandler, SiteDllFile" />

- To

<add verb="*" path="*.xml" type="Company.XmlHandler, NewMvcSiteDllFile" />

Problem 4

The form values security and validation seems to have been tightened-up in ASP.NET 4.0 or something, because I was getting an exception when reading Form values containing XML fragments. This was remedied with this config setting:

<httpRuntime requestValidationMode="2.0"/>

Problem 5 – At this stage, there has been no problem 4

With everything else copied over and some shared components refactored out into a shared library, everything else is working.

Labels: , ,

Helpful docs when hardening an IIS web server

Sunday, November 21, 2010 / Posted by Luke Puplett / comments (0)

While clearing up some crap on my desktop I came across a note listing some documents I’d used when preparing my web server for co-location and exposure to the harshness of the public internet.

How To: Harden the TCP/IP Stack
http://msdn.microsoft.com/en-us/library/ff648853.aspx

TCP Receive Window Size and Window Scaling
http://msdn.microsoft.com/en-us/library/ms819736.aspx

How To: Protect Forms Authentication in ASP.NET 2.0
http://msdn.microsoft.com/en-us/library/ff648341.aspx

How To: Perform a Security Deployment Review for ASP.NET 2.0
http://msdn.microsoft.com/en-us/library/ff647403.aspx

How To: Use IPSec for Filtering Ports and Authentication
http://msdn.microsoft.com/en-us/library/ff648481.aspx

How To: Use IISLockdown.exe
http://msdn.microsoft.com/en-us/library/ff650415.aspx

Labels: , , ,

The (dis)appearance of ‘Spaghetti Code’ in ASPX views

Friday, November 05, 2010 / Posted by Luke Puplett / comments (0)

Offended by the proliferation of vulgar <% throughout my view mark-up, and with no luck/patience getting Razor working, I decided that the poor man’s version would be to tone-down those ugly little marks. The result was quite amazing.

Capture

The tags denoting server-side code no longer wrestle for attention and send my eyes spinning. I almost didn’t post this but I’d wager that most people out there would blame the hard readability on the way ASP.NET MVC works and not the poor choice of colour in the default settings.

Labels: ,

ASP.NET MVC Robust HyperLinks

Sunday, September 05, 2010 / Posted by Luke Puplett / comments (0)

Motivated by ActionLink failing to produce proper MVC-style / / / links and, although RouteLink does work, I think route names and possibly all route logic should be kept out of the view, I decided to bake my own link maker, and make the links in the controller and put them in the model. Here’s how I did that.

I have a BaseSiteNameController in which helper methods go. To this, I added the following method:

public static string BuildLink(
    RequestContext requestContext,
    string routeName,
    string action,
    string controller,
    object routeValues)
{
    UrlHelper u = new UrlHelper(requestContext);

    var rvd = new RouteValueDictionary(routeValues);
    rvd.Add("controller", controller);
    rvd.Add("action", action);

    var httpContext = System.Web.HttpContext.Current;

    return UrlHelper.GenerateUrl(
        routeName, null, null,
        httpContext.Request.Url.Scheme, 
        httpContext.Request.Url.DnsSafeHost, 
        null, rvd, u.RouteCollection, 
        requestContext, true);
}

Note that I specify controller and action in the RouteValueDictionary even though GenerateUrl has parameters for controller and action. When these are used, it kicks out old-skool URLs – I think these params were designed to be used when a route name is not supplied. The GenerateUrl method smells like it wasn’t designed for general direct use.

GenerateUrl is the thing that does the magic; it looks up the route and works out how the URL should be structured.

It’s a static method because I also want to be able to call it from my models. Some of my models have sub-models and some of those have links. I figured that a View is for layout, and links kind of straddle both sides but the logic required to make them, tips them into the non-view side, in my opinion.

My models aren’t always created and prepared by my controllers. Some of my models contain a small amount of logic to populate themselves, so I want them to be able to call this method* when they populate their links (e.g. FavouriteBooksModel.AddBookLink)

Note also that the method takes a RequestContext. I now have to have this context available in my models, so I pass it down using a CustomerWebProfile class that I already use to flow important data into my models, such as TimeZone data. Each model has a CustomerWebProfile property (inherited via a base model).

*Except that my models don’t directly call BuildLink because I wrap these calls helper methods.

Under each action method, I add a method to build a link to the method. I do this because I can prevent the caller from having to know the action name, and so I can refactor it easily.

public static string BuildLinkToCustomerAccount(
    System.Web.Routing.RequestContext requestContext, string customerId)
{
    return BuildLink(requestContext, "Default", "CustomerAccount", "Account",
        new { data = customerId });
}

Now my models use these helper methods to make their links (code in model).

public string CustomerAccountLink
{
    get
    {
        return Controllers.PlannerController.BuildLinkToRecordOneTime(
            this.WebProfile.RequestContext, this.TransmissionKeys);
    }
}

If I change my controllers or actions, I can change the helper method, and renames can be done without worrying about strings in aspx pages!

My views now make links in the normal way, like this:

<a href="<%: Model.CustomerAccountLink %>" ><%: Html.GetLocalString("Your Account") %></a>


And if I change the model, my page won’t compile and I can sort them out before runtime.

Labels: , , ,

ASP.NET MVC 2 RedirectToSignin

Thursday, August 19, 2010 / Posted by Luke Puplett / comments (1)

If, for whatever reason, you cannot use the [Authorize] attribute on an action method, or as in my case, you have an unusual architecture, then this helpful method directs a visitor back to the sign-in or login screen and then re-runs the original action. It’s designed to work exactly as the AuthorizeAttribute works, but with the difference that you can do your own IsAuthorised logic within the method.

An application I’m working on has a WCF service which is where customers are logged-in. The MVC application simply packages up the web front end and ships all requests, CRUD ops, everything into service calls and then paints the results out via ASP.NET/HTML.

When a customer logs in to my MVC 2 app, it’s really just calling Login on the WCF Authentication Service – the MVC app doesn’t keep track of sessions and is truly stateless. The MVC app does, however, use forms authentication and so the cookie can say “yep, customer is signed-in” while the WCF service says, “Uh uh. This customer’s session expired.”

This means that my action methods with [Authorize] run, but then fail. I wrote this method to redirect the customer to the sign-in box, and then continue to execute the original action, using the returnUrl.

Code:


protected ActionResult RedirectToSignin(
    string returnAction, 
    string returnController, 
    object returnRouteValues, 
    RequestContext requestContext)
{
    UrlHelper u = new UrlHelper(requestContext);
            
    string returnUrl = UrlHelper.GenerateUrl(
        null, returnAction, returnController, new RouteValueDictionary(returnRouteValues),
        u.RouteCollection, requestContext, true);

    string baseAddress = String.Format("{0}://{1}"
        HttpContext.Request.Url.Scheme, HttpContext.Request.Url.Authority);

    return Redirect(String.Format("{0}/Customer/Signin?returnUrl={1}"
        baseAddress, returnUrl));
}

Now within my action method, if I get a null or a fault from my service, I return RedirectToSignin(xyz) instead of returning an error. After sign-in, the action is called again and all is good in the hood.

Labels: , , ,

Email to Phil Haack on MVC Routing

Wednesday, August 11, 2010 / Posted by Luke Puplett / comments (1)

Hi Phil

Congratulations on MVC, I can say that it's the first major MS framework I've enjoyed in a while, due to its simplicity.

Can I suggest a non-matching style routing system for MVC 4?

The route-matching logic is prone to problems, see link, and although there may be an answer, I don't care; as a 'user' of your technology, I want to go home happy and make progress on my project. I just want it to work.

My idea is to have an attribute on each and every action method that sets the route, optional default values for the params and IsDefaultAction for the controller.

A full list of all controllers and routes can then be made and the need to 'match' is eliminated.

The only problem might be that attributes are fussy about using constants, so maybe this would suffice if the attrib won't take an anonymous type:

[Route(Format = "{controller}/{id}/{action}")]
[DefaultAction]
[DefaultParamValue(Param = "id", Value = "")]
public ActionResult ... (string id) { ... }

And for, controller-less and action-less URLs:

[Route(Format = "{year}/{month}/{day}")]
public ActionResult ... (int year, int month) { ... }

Which would throw because the day parameter is missing, making route misconfiguration easier to discover. The Controller and Action method to call is inferred from what the attribute decorates. If you wanted to allow it, the controller no longer needs to be named XyzController.

Furthermore, if someone defines two controller-less and action-less routes, both with 3 params, then this can be caught and thrown when the route table is built instead of only being discoverable when a request comes in.

For example, if I also add to a different method:

[Route(Format = "{country}/{case}/{agent}")]

Then it should detect that this conflicts with the one above. There's no controller name or action name in the URL to assist routing and both take 3 params.

I'm sure I've missed out some key things that the current system permits, but as I said, I don't want it to be smart and enigmatic. I want it to work, or clearly direct me to the problem when I mess up.

Thanks for listening.

Luke

Labels: , ,

How to: Send Email from a Microsoft Server Application

Friday, June 18, 2010 / Posted by Luke Puplett / comments (0)

If you have a server application, web site or even a job or task running on a Windows server, you may need to send out emails. For me, it was an ASP.NET MVC 2 application; I needed to send an email to people who stuck their address in the interested parties register on the coming soon page on vuPlan.tv to confirm it was a genuine request. But you could have a build server that you want to email out a report from or some other thing. This guide is more about the server side setup than the SMTP client you send, although I will give examples in .NET and DOS, via Blat.exe.

To make things easy, I’ll bullet the whole process.

  • First, ensure that you cannot use an existing Exchange Server or MTA in your organisation. It makes life so much easier when someone else deals with these things because, as the person coding or scripting, you should only care about getting the job done.

  • After you’ve failed to convince the jobsworth in IT security, install your own one. In Windows Server 2008 (Web Edition, also) open Server Manager > Features node > Right-click > Add Feature > locate SMTP Server and install the requisite IIS 6 components as well as Telnet Client.

  • If you are using a hosted or public internet facing server, you should now ensure that your server had a PTR Record for its IP address. The people that gave you your RIPE internet address can do this (usually your host or ISP). Do this now so they can get started.

  • To prevent spam, most MTAs will do a reverse DNS (rDNS) lookup on your server to ensure you have the same domain name as you claim to be sending email from. The style of address your server has can also trigger a spam detector, so avoid a reverse DNS address such as 100-99-98-97.dynamic.mydomain.com as it looks like the dynamic ranges given out to broadband home users (who are blacklisted on SMTP servers by default).

  • Check http://spamhaus.org and make sure that your IP address is not blacklisted before you begin.

  • Set the Simple Mail Transport Protocol service to start automatically.

  • You may want to add an smtp CNAME into your DNS server so you can move it in future without editing code or config files.

  • In IIS 7 Manager, locate your site and then the SMTP E-mail icon and set the email address and check the radio button to deliver to localhost on port 25, or the DNS alias you setup. Two things to note: setting delivery to a pick-up directory is useful on development boxes when you don’t want to flood the mail servers with real email. The .eml files can be opened in Outlook. Also, the other settings on this page may actually be ignored by clients/your app, but I’m setting them in any case.

  • Open IIS 6 Manager and you should just have the SMTP node in there.

  • Right-click the SMTP virtual server and go to Properties.

  • Tab 1: Tick Enable logging and set a valid path.

  • Tab 2: Under Authentication, set Anonymous access only for the time being. Under Connection, check All except the list below and under Relay, check All except the list below and tick Allow all computers at the bottom.

  • Tab 3: Check the Badmail directory you can fill in the Send copy… box but it may not work anyway, at first. Leave the other settings.

  • Tab 4: Set all timeouts and delays to minimum. This is so the server stops trying and gives you an error to look at quickly. You should reset these after setting up/troubleshooting. Enter Outbound Security and make sure that its set to Anonymous.

  • You should review the other tabs but otherwise leave them for now and click OK.

  • Now rename the domain to just the domain part and close IIS 6 Manager.

  • Open a DOS prompt and test that your SMTP server is listening. telnet localhost 25 or whatever your server name is. Note “Microsoft ESMTP MAIL Service, Version: 7.5”: 7.5 eh? What’s all this needing IIS 6 all about then?

  • Download Blat.exe and ‘install’ a default profile like so:

    blat -install -f fromaddress@mydomain.com -server localhost -port 25 -try 1 -profile myprofile
  • Now you can try sending a test message like so:

    blat - -to me@hotmail.com -f me@mydomain.com -p myprofile -body "This is a test." -subject "This is a test message."
  • Hotmail has a very clearly written Non-delivery Report (NDR) message, which is why I choose them, also you’ll hear the ping from Live Messenger if it works.

  • Remember to check your Junk folder!

  • Open Event Viewer and ensure nothing was logged by the SMTP service. I use a catch all filter for “Everything in the last hour”.

  • Open the c:\inetpub\mailroot folder and keep an eye on what’s in each of the folders. Personally, I’ve found that Badmail doesn’t seem to gather anything and that the Drop folder has the most interesting information. EML files can be opened in Notepad and the Drop folder is like the Inbox; it will receive NDRs that contain error messages.

  • Also, check the SMTP service log file. A successful connection should be visible in the log as OutboundConnectionResponse lines. If these are not there, then it is having a problem connecting to the remote mail server.

  • Check that the remote MTA is up. Use http://mxtoolbox.com to find the MX record and thus the mail server for the recipient. Check it’s up.

  • Ensure that port 25 is open in your firewall.

  • Attempt to telnet to the remote server.

  • Give up and use http://Jangomail.com

When you get Blat.exe sending successfully, then you can use the SmtpClient class to very easily fire out your emails from your ASP.NET app.

Good luck!

Labels: , , ,

Note to self: Web Page Performance Optimization - Notes from MIX10

Tuesday, March 23, 2010 / Posted by Luke Puplett / comments (0)

My personal notes from Jason Weber's session at MIX10. Essentially just a bulleted list of the 20 or so recommendations he made.

  • Compress HTTP but never images.
  • Provide cachable content with future expiry date.
  • Use conditional requests, GET if modified since.
  • Minify JavaScript; can reduce by 25%.
  • Don't scale images, resample them.
  • Use image sprites.
  • Avoid inline JavaScript.
  • Linking JavaScript in head prevents HTML render, or use defer="defer"
  • Avoid embedded styles.
  • Only send styles actually required by the page - server side plug-ins can automate this.
  • Link CSS at top in head.
  • Avoid using @import for hierarchical styles, place them in HTML head.
  • Minimize symbol resolution, cache in method body. Functions also; create function pointers in local method.
  • Use JSON native methods if available.
  • Remove duplicate script file declarations.
  • Minimize DOM interactions, cache references.
  • Built-in DOM methods are always faster.
  • Use selector APIs.
  • Complex element selectors are slow; favor class selectors and child instead of descendant.
  • Apply visual changes in batches.

Labels: , , , , , ,