14

I'm having a problem where users on mobile devices are encountering an error in MVC that does not occur when viewing the site on a regular desktop. I can consistently reproduce the error by using Chrome's developer tools and applying any other UA than the default.

The underlying exception thrown is: ArgumentException: Illegal characters in path. at System.IO.Path.CheckInvalidPathChars(String path, Boolean checkAdditional) at System.IO.Path.GetExtension(String path) at System.Web.WebPages.DefaultDisplayMode.TransformPath(String virtualPath, String suffix) at System.Web.WebPages.DefaultDisplayMode.GetDisplayInfo(HttpContextBase httpContext, String virtualPath, Func'2 virtualPathExists) at System.Web.WebPages.DisplayModeProvider.GetDisplayInfoForVirtualPath(String virtualPath, HttpContextBase httpContext, Func'2 virtualPathExists, IDisplayMode currentDisplayMode, Boolean requireConsistentDisplayMode) at System.Web.Mvc.VirtualPathProviderViewEngine.GetPathFromGeneralName(ControllerContext controllerContext, List'1 locations, String name, String controllerName, String areaName, String cacheKey, String[]& searchedLocations) at System.Web.Mvc.VirtualPathProviderViewEngine.GetPath(ControllerContext controllerContext, String[] locations, String[] areaLocations, String locationsPropertyName, String name, String controllerName, String cacheKeyPrefix, Boolean useCache, String[]& searchedLocations) at System.Web.Mvc.VirtualPathProviderViewEngine.FindPartialView(ControllerContext controllerContext, String partialViewName, Boolean useCache) at System.Web.Mvc.ViewEngineCollection.<>c__DisplayClass2.<FindPartialView>b__1(IViewEngine e) at System.Web.Mvc.ViewEngineCollection.Find(Func'2 lookup, Boolean trackSearchedPaths) at System.Web.Mvc.ViewEngineCollection.FindPartialView(ControllerContext controllerContext, String partialViewName) at System.Web.Mvc.Html.TemplateHelpers.ExecuteTemplate(HtmlHelper html, ViewDataDictionary viewData, String templateName, DataBoundControlMode mode, GetViewNamesDelegate getViewNames, GetDefaultActionsDelegate getDefaultActions) at System.Web.Mvc.Html.TemplateHelpers.TemplateHelper(HtmlHelper html, ModelMetadata metadata, String htmlFieldName, String templateName, DataBoundControlMode mode, Object additionalViewData, ExecuteTemplateDelegate executeTemplate) at System.Web.Mvc.Html.TemplateHelpers.TemplateHelper(HtmlHelper html, ModelMetadata metadata, String htmlFieldName, String templateName, DataBoundControlMode mode, Object additionalViewData) at System.Web.Mvc.Html.TemplateHelpers.TemplateFor[TContainer,TValue](HtmlHelper'1 html, Expression'1 expression, String templateName, String htmlFieldName, DataBoundControlMode mode, Object additionalViewData, TemplateHelperDelegate templateHelper) at System.Web.Mvc.Html.TemplateHelpers.TemplateFor[TContainer,TValue](HtmlHelper'1 html, Expression'1 expression, String templateName, String htmlFieldName, DataBoundControlMode mode, Object additionalViewData) at System.Web.Mvc.Html.DisplayExtensions.DisplayFor[TModel,TValue](HtmlHelper'1 html, Expression'1 expression)

Using fiddler, the only difference in requests when comparing a successful one to a failed request is the User-Agent (and the cache buster appended by jQuery as part of the query string parameters).

Why is only changing the UA causing this exception and how can I avoid this issue without writing a specific work around into the system for every place that this is and could occur?

SignalRichard
  • 1,572
  • 2
  • 17
  • 32
  • @RomanMik - I actually found the same work around as CSJ below which is to avoid using yield blocks in my view models. After I changed the code to materialize the list instead of using yield the issue was resolved. I still don't fully understand why this occurs in .NET with certain user agents but at least there's a standard way to work around it. – SignalRichard Jan 21 '15 at 15:47
  • I found a different solution related to ASP.NET DisplayModeProvider that I shared in related SO article http://stackoverflow.com/questions/33694842/illegal-characters-in-path-depending-on-user-agent/40229384#40229384 – Mark Nadig Oct 26 '16 at 22:21

2 Answers2

11

I had the exact same problem, and fixed it.

My problem turned out to be the use of a yield block in my viewmodel:

Controller:

var vm = new BigVM {
    SmallVMs = BuildSmallOnes()
};
return View(vm);

private IEnumerable<SmallVM> BuildSmallOnes()
{
    // complex logic
    yield return new SmallVM(1);
    yield return new SmallVM(2);
}

View:

@model BigVM
@Html.DisplayFor(x => x.SmallVMs)   <-- died

Inexplicably, this worked for desktops but failed for iPads and iPhones, citing the exact same stacktrace. Similar problems were reported here and here. The problem was solved by adding a .ToList() call, thus:

var vm = new BigVM {
    SmallVMs = BuildSmallOnes().ToList()
};

Presumably the class that the compiler generates to represent the yield block includes some characters that some User Agents just don't like. Including the ToList() call uses a List<> instead.

Community
  • 1
  • 1
CSJ
  • 3,641
  • 2
  • 19
  • 29
  • `ToList()` fixed the problem for me too. Just using `Where(...)` seems to work fine (so the actual type passed to the view is `WhereListIterator`), but after applying `OrderBy(...).Take(...)` (so the end result is `Enumerable....`), the view crashed on mobile devices. – Arve Systad Mar 31 '16 at 07:52
  • 1
    I guess the problem exists for several of the IEnumerable types; Maybe using IList (and thus stopping you from forgetting `ToList()`) in view models should be a "best practice" to create a compile time safety net? – Arve Systad Mar 31 '16 at 07:59
  • I've just had the very same problem. IEnumerable and yield used by a viewmodel builder. Does anyone know what, exactly, causes the problem? This smells like a bug in the view engine. – Øyvind Apr 08 '16 at 20:35
  • life saver. I didn't know class names ended up as paths; that's kinda nuts. – toddmo Mar 29 '17 at 19:42
0

I believe that the reason we are having this issue is MultipleViews and DisplayMode providers introduced for handling mobile devices separately. This is code that is still in .NET 4 but has been deprecated.

A solution to disable the functionality entirely can be found here. Illegal characters in path depending on User-Agent?

Samuel Liew
  • 76,741
  • 107
  • 159
  • 260
HXavS
  • 119
  • 7
  • 1
    A link to a solution is welcome, but please ensure your answer is useful without it: [add context around the link](//meta.stackexchange.com/a/8259/165483) so your fellow users will have some idea what it is and why it’s there, then quote the most relevant part of the page you're linking to in case the target page is unavailable. [Answers that are little more than a link may be deleted.](//stackoverflow.com/help/deleted-answers) – Samuel Liew May 01 '20 at 02:48
  • Sure thing. Was really meant as a comment to be helpful but left it as answer since haven't been really active on here and didn't have required rep score. – HXavS Jul 23 '20 at 18:16