4

I've recently installed T4MVC using Nuget. I had an old version before this that worked fine, however I had a new requirement that needed an asynchronous controller. After creating the asynchronous controller my project would not compile due to something to do with t4. So I updated using Nuget.

The problem I have now is that my controller worked fine until I recently refreshed t4mvc. After refreshing my code compiles, but when I call the async action it returns after a long time and returns a massive amount of incorrect data. Furthermore and critically, if I set breakpoints in my controller, it no longer hits them?! If I revert my DocumentController.generated.cs to the last working version everything works fine.

Below are my old DocumentController.generated.cs that works and the new one that doesn't.

Can anyone help me figure out what's going on here? I've become reliant on T4MVC because it's so good, however I really can't avoid these async actions.

OLD

// <auto-generated />
// This file was generated by a T4 template.
// Don't change it directly as your change would get overwritten.  Instead, make changes
// to the .tt file (i.e. the T4 template) and save it to regenerate this file.

// Make sure the compiler doesn't complain about missing Xml comments
#pragma warning disable 1591
#region T4MVC

using System;
using System.Diagnostics;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Web;
using System.Web.Hosting;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Web.Routing;
using T4MVC;
namespace WebUI.Client.Controllers {
    public partial class DocumentController {
        [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
        public DocumentController() { }

        [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
        protected DocumentController(Dummy d) { }

        [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
        protected RedirectToRouteResult RedirectToAction(ActionResult result) {
            var callInfo = result.GetT4MVCResult();
            return RedirectToRoute(callInfo.RouteValueDictionary);
        }


        [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
        public DocumentController Actions { get { return MVC.Document; } }
        [GeneratedCode("T4MVC", "2.0")]
        public readonly string Area = "";
        [GeneratedCode("T4MVC", "2.0")]
        public readonly string Name = "Document";

        static readonly ActionNamesClass s_actions = new ActionNamesClass();
        [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
        public ActionNamesClass ActionNames { get { return s_actions; } }
        [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
        public class ActionNamesClass {
            public readonly string Index = "Index";
        }


        static readonly ViewNames s_views = new ViewNames();
        [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
        public ViewNames Views { get { return s_views; } }
        [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
        public class ViewNames {
            public readonly string Index = "~/Views/Document/Index.aspx";
        }
    }

    [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
    public class T4MVC_DocumentController: WebUI.Client.Controllers.DocumentController         

    {
        public T4MVC_DocumentController() : base(Dummy.Instance) { }

        public override System.Web.Mvc.ActionResult Index() {
            var callInfo = new T4MVC_ActionResult(Area, Name, ActionNames.Index);
            return callInfo;
        }

    }
}

#endregion T4MVC
#pragma warning restore 1591

NEW

// <auto-generated />
// This file was generated by a T4 template.
// Don't change it directly as your change would get overwritten.  Instead, make changes
// to the .tt file (i.e. the T4 template) and save it to regenerate this file.

// Make sure the compiler doesn't complain about missing Xml comments
#pragma warning disable 1591
#region T4MVC

using System;
using System.Diagnostics;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Web;
using System.Web.Hosting;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Web.Routing;
using T4MVC;
namespace WebUI.Client.Controllers {
    public partial class DocumentController {
    [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
    public DocumentController() { }

    [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
    protected DocumentController(Dummy d) { }

    [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
    protected RedirectToRouteResult RedirectToAction(ActionResult result) {
        var callInfo = result.GetT4MVCResult();
        return RedirectToRoute(callInfo.RouteValueDictionary);
    }

    [NonAction]
    [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
    public System.Web.Mvc.JsonResult GetDocumentListCompleted() {
        return new T4MVC_JsonResult(Area, Name, ActionNames.GetDocumentListCompleted);
    }
    [NonAction]
    [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
    public System.Web.Mvc.ActionResult GetDocumentThumbnailCompleted() {
        return new T4MVC_ActionResult(Area, Name, ActionNames.GetDocumentThumbnailCompleted);
    }

    [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
    public DocumentController Actions { get { return MVC.Document; } }
    [GeneratedCode("T4MVC", "2.0")]
    public readonly string Area = "";
    [GeneratedCode("T4MVC", "2.0")]
    public readonly string Name = "Document";

    static readonly ActionNamesClass s_actions = new ActionNamesClass();
    [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
    public ActionNamesClass ActionNames { get { return s_actions; } }
    [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
    public class ActionNamesClass {
        public readonly string Index = "Index";
        public readonly string GetDocumentListCompleted = "GetDocumentListCompleted";
        public readonly string GetDocumentThumbnailCompleted = "GetDocumentThumbnailCompleted";
    }


    static readonly ViewNames s_views = new ViewNames();
    [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
    public ViewNames Views { get { return s_views; } }
    [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
    public class ViewNames {
        public readonly string Index = "~/Views/Document/Index.aspx";
    }
}

[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class T4MVC_DocumentController: WebUI.Client.Controllers.DocumentController {
    public T4MVC_DocumentController() : base(Dummy.Instance) { }

    public override System.Web.Mvc.ActionResult Index() {
        var callInfo = new T4MVC_ActionResult(Area, Name, ActionNames.Index);
        return callInfo;
    }

    public override System.Web.Mvc.JsonResult GetDocumentListCompleted(Web.Gateway.DocumentMetaDataCollection clientDocuments) {
        var callInfo = new T4MVC_JsonResult(Area, Name, ActionNames.GetDocumentListCompleted);
        callInfo.RouteValueDictionary.Add("clientDocuments", clientDocuments);
        return callInfo;
    }

    public override System.Web.Mvc.ActionResult GetDocumentThumbnailCompleted(System.IO.Stream document, string type) {
        var callInfo = new T4MVC_ActionResult(Area, Name,    ActionNames.GetDocumentThumbnailCompleted);
            callInfo.RouteValueDictionary.Add("document", document);
            callInfo.RouteValueDictionary.Add("type", type);
            return callInfo;
        }

    }
}

#endregion T4MVC
#pragma warning restore 1591

As requested here's the stubs of my DocumentController class;

namespace WebUI.Client.Controllers
{
    [Activated]
    [ConcurrentSessionDisabled]
    public partial class DocumentController : AsyncController
    {
    public readonly HashSet<string> ImageTypes = new HashSet<string>(new[] { "jpeg", "jpg", "png", "tif", "tiff", "bmp" });
    public readonly HashSet<string> IgnoredImageTypes = new HashSet<string>(new[] { "tif", "tiff" });

    public virtual ActionResult Index()
    {
        return RedirectToAction("List");
    }

    public virtual ActionResult List()
    {
        return View();
    }

    public virtual ActionResult Thumbnails(int id)
    {
        ViewData["documentId"] = id;
        return View();
    }

    public void GetDocumentListAsync()
    {
        AsyncManager.Timeout = 30000;
        AsyncManager.OutstandingOperations.Increment();

        Task.Factory.StartNew((state) => {
            Tuple<int, int> clientDetails = (Tuple<int, int>)state;

            DocumentMetaDataCollection clientDocuments = DocumentHelper.GetClientDocuments(clientDetails.Item1, clientDetails.Item2);
            AsyncManager.Parameters["clientDocuments"] = clientDocuments;

            AsyncManager.OutstandingOperations.Decrement();

        }, new Tuple<int, int>(Profile.User().ClientId, Profile.User().CrmAccountId));
    }

    public virtual JsonResult GetDocumentListCompleted(DocumentMetaDataCollection clientDocuments)
    {
        return Json(from document in clientDocuments.Documents
                    select new
                    {
                        Id = document.Id,
                        Values = document.Values,

                    }, JsonRequestBehavior.AllowGet);
    }

    public void GetDocumentFilenamesAsync(int id)
    {
        AsyncManager.Timeout = 5000;
        AsyncManager.OutstandingOperations.Increment();

        Task.Factory.StartNew((state) =>
        {
            int documentId = (int)state;

            List<string> urls = DocumentHelper.GetUrlsForDocument(documentId);

            AsyncManager.Parameters["documentId"] = documentId;
            AsyncManager.Parameters["urls"] = urls;

            AsyncManager.OutstandingOperations.Decrement();
        }, id);
    }

    public JsonResult GetDocumentFilenamesCompleted(int documentId, List<string> urls)
    {
        IDictionary<int, string> filenameToUrl = new Dictionary<int, string>();
        Regex illegalCharacters = new Regex("[^A-Za-z0-9_[-]:]");
        int idCounter = 0;

        foreach(string url in urls)
        {
            filenameToUrl.Add(++idCounter, url);
        }

        session_HoldDocumentUrls(documentId, filenameToUrl);

        return Json(from file in filenameToUrl select new {
            Name = filenameFrom(file.Value), 
            Id = file.Key
        });
    }

    private string filenameFrom(string url)
    {
        return url.Substring(url.LastIndexOf('/') + 1, url.Length - (url.LastIndexOf('/') + 1));
    }

    public void GetThumbnailAsync(int documentId, int imageId)
    {
        AsyncManager.Timeout = 5000;
        AsyncManager.OutstandingOperations.Increment();

        string url = session_GetDocumentUrls(documentId).First(f => f.Key == imageId).Value;

        Task.Factory.StartNew((state) => {
            string fileUrl = (string)state;
            thumbnailLoader(fileUrl);
        }, url);
    }

    private void thumbnailLoader(string fileUrl)
    {
        Stream document = DocumentHelper.GetDocument(fileUrl);
        string type = fileUrl.Substring(fileUrl.LastIndexOf('.') + 1, fileUrl.Length - (fileUrl.LastIndexOf('.') + 1)).ToLower();

        if (ImageTypes.Contains(type))
        {
            document = ImageHelper.ResizeImage(document, 210);
            type = "png";
        }

        AsyncManager.Parameters["document"] = document;
        AsyncManager.Parameters["type"] = type;

        AsyncManager.OutstandingOperations.Decrement();
    }

    public virtual ActionResult GetThumbnailCompleted(Stream document, string type)
    {
        switch (type)
        {
            case "png":
                return new FileStreamResult(document, "image/png");
            case "pdf":
                return File(Links.Content.Images.pdf_256_png, "image/png");
            default:
                return File(Links.Content.Images.document_256_png, "image/png");
        }
    }

    public void FileAsync(int documentId, int imageId)
    {
        if (session_GetDocumentUrls(documentId) == null)
        {
            return;
        }

        string url = session_GetDocumentUrls(documentId).First(f => f.Key == imageId).Value;

        AsyncManager.Timeout = 5000;
        AsyncManager.OutstandingOperations.Increment();

        Task.Factory.StartNew((state) =>
        {
            string fileUrl = (string)state;

            Stream document = DocumentHelper.GetDocument(fileUrl);

            string type = fileUrl.Substring(fileUrl.LastIndexOf('.') + 1, fileUrl.Length - (fileUrl.LastIndexOf('.') + 1)).ToLower();

            if (ImageTypes.Contains(type) && !IgnoredImageTypes.Contains(type))
            {
                document = ImageHelper.ConvertToPng(document);
                type = "png";
            }

            AsyncManager.Parameters["document"] = document;
            AsyncManager.Parameters["type"] = type;

            AsyncManager.OutstandingOperations.Decrement();
        }, url);
    }

    public virtual ActionResult FileCompleted(Stream document, string type)
    {
        if (document == null || type == null)
        {
            return RedirectToAction("List");
        }

        return new FileStreamResult(document, MimeHelper.Lookup("." + type));
    }
}

}

UPDATE 7/4/11

Hi David, Apologies for taking so long to get back to you, I've been looking into some other work.

I've managed to track down the problem and hopefully you can shed some light on it and possibly create a fix.

If you look at the second code dump above, this is the one with the problem. The problem is that you've created;

[NonAction]
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public System.Web.Mvc.JsonResult GetDocumentListCompleted() {
    return new T4MVC_JsonResult(Area, Name, ActionNames.GetDocumentListCompleted);
}

I didn't think MVC was throwing an error, but it was;

2011-04-07 15:59:38,921 [41] FATAL MvcApplication [(null)] - An uncaught exception occurred System.Reflection.AmbiguousMatchException: Lookup for method 'GetDocumentListCompleted' on controller type 'DocumentController' failed because of an ambiguity between the following methods: System.Web.Mvc.JsonResult GetDocumentListCompleted(Web.Gateway.DocumentMetaDataCollection) on type WebUI.Client.Controllers.DocumentController System.Web.Mvc.JsonResult GetDocumentListCompleted() on type WebUI.Client.Controllers.DocumentController at System.Web.Mvc.Async.AsyncActionMethodSelector.GetMethodByName(String methodName)

So as I understand it, the MVC system has an ambiguous match with my code and the code you've created.

If I comment out your above code then everything runs normally again.

Do you understand the nature or the problem now? Can it be fixed?

Cheers,

Ryan.

Ryan Worsley
  • 655
  • 1
  • 4
  • 17

1 Answers1

3

Update (4/12/2011): ok, I just posted a new build of T4MVC (2.6.51) that ignored async completion methods. This should fix the issue for you!


T4MVC doesn't support async action. That is, you can certainly have async actions in your controllers, but you cannot refer to them using T4MVC

Please see this previous question on that topic: AsyncController in MVC2 and T4MVC: can they work together?

Community
  • 1
  • 1
David Ebbo
  • 42,443
  • 8
  • 103
  • 117
  • Hi David, Thanks for responding. I looked at that link, is there a way I can tell T4MVC to ignore my async controller? – Ryan Worsley Mar 25 '11 at 09:04
  • I thought it would be ignoring it already. Can you update your question with more info about whan your controller and actions look like? I don't need the code inside the methods, but at least the declaration. Also, please include the full error that you are getting. – David Ebbo Mar 25 '11 at 18:05
  • Hi David, I've updated the question above with my controller. Basically I don't get an error, the scenario is this: 1. Code runs fine, 2. I save T4MVC.cs 3. Your template then creates a new DocumentController.generated.cs 4. My controller just hangs when calling action methods?! 5. Setting breakpoints in my action methods never hits them?! 6. I undo T4MVC by undoing my checkout from source control 7. Everything works fine again. -- Hope this helps you figure it out, I love T4MVC and it would suck to try and rip it out. – Ryan Worsley Mar 28 '11 at 07:46
  • When you refer to 'calling your action methods', what do you mean? Normally, action methods are not meant to be called directly, and are instead called via routing (other than when making T4MVC pseudo-calls, which you're not doing). Also, do you see any calls to the T4MVC generated methods? You shouldn't unless you try to call them via MVC.Document (which you can't do for async methods). If we can't figure it out, you may have to send me a code sample with a minimal repro. – David Ebbo Mar 29 '11 at 07:31
  • I mean calling them via the routing system, of course. I'll do a bunch of testing this week and get back to you. Ryan. – Ryan Worsley Mar 29 '11 at 08:53
  • Hi David, I've added an update above that attempts to explain the problem and how I've worked round it. Would you mind taking a look and having a think about it? (Marked as UPDATE 7/4/11) Kind regards, Ryan. – Ryan Worsley Apr 07 '11 at 15:26