I have a very basic Xamarin.Forms app, where I try to access an Image. This works fine when the image is in the necessary folder e.g
\Resources\btc.png for iOS
\Resouces\Drawable\btc.png for Android
Root folder\btc.png for UWP
However when I add a folder into the mix it doesn't display anything:
\Resources\Logos\btc.png for iOS
\Resouces\Drawable\Logos\btc.png for Android
Root folder\Logos\btc.png for UWP
Here is the line I am using:
<Image Source="btc.png" HeightRequest="20" WidthRequest="20" />
Here is the line that doesn't work:
<Image Source="Logos\btc.png" HeightRequest="20" WidthRequest="20" />
I have triple checked all backslashes and spellings/capital letters.
TL;DR: Do not use sub-folders. ;-)
On Android when using subfolders within the Drawable folder (and the pixel density folders, i.e: drawable-xxhpdi), the sub-folder name is ignored but the drawable's ID are generated in the Java R. file (C# Resources. in Xamarin.Android), assuming there is no invalid names or clashing. So in native Android & Xamarin.Android those drawable will have an resource ID (integer-based) assigned to them and are usable.
But, Xamarin.Forms will not be able to find those images as a reverse lookup is used, from Name to Android resource ID and thus will be no match.
Also on iOS, the use of the Resource folder for images via BundleResource is deprecated and you should be using "Asset Catalog Image Sets" instead.
For more info: Images in Xamarin.Forms
Yes unfortunately you can't make use of sub-folders for android images but you can for the other 2 platforms and to account for the difference here's what I typically do.
Use the following AssetPathHelper (modify for your needs, in the below I only use a sub-folder for UWP images, and for lottie animations I use a sub-folder in both UWP and iOS). Also I assume .png, if you use other image types then need to handle that.
public static class AssetPathHelper
{
public static readonly string UWPimgRoot = #"Assets\Images\";
public static readonly string UWPanimRoot = #"Assets\Animations\";
public static readonly string iOSanimRoot = #"Animations/"; //note the different slash here, not sure if it's required but that is what works
public static string GetImageSource(string resourceName)
{
var imgFileName = resourceName + ".png"; //! obviously this requires all images used to be pngs
if (Device.RuntimePlatform != Device.UWP)
return imgFileName;
return UWPimgRoot + imgFileName;
}
public static string GetLottieAnimSource(string resourceName)
{
var animFileName = resourceName + ".json";
switch (Device.RuntimePlatform)
{
case Device.iOS:
return iOSanimRoot + animFileName;
case Device.UWP:
return UWPanimRoot + animFileName;
}
return animFileName;
}
}
which gets used in the following Converter:
/// <summary>
/// Provides the path to the image taking into account the platform specific location.
/// Can be used without a real binding (i.e. when only a parameter is provided)
/// </summary>
public class ImagePathConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null)
return AssetPathHelper.GetImageSource(parameter.ToString());
return AssetPathHelper.GetImageSource(value.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
So now in my XAML I can just do this, either as a real binding if the image will be changing or if not, then just passing the image name as the converter parameter:
<Image
Source="{Binding ConverterParameter=logo_splash, Converter={StaticResource ImagePathConverter}}"/>
Related
I am trying to use a custom (derived) RazorViewEngine AND precompiled views using RazorGenerator.
Some context:
We have a base product that we use for multiple client implementations. With that we have a core set of base views. Most of the views work most of the time. Right now we end up copying existing views for each new solution and modifying as needed. This ends up with 95% of the views being the same between clients and 5% changed.
What I want to do take a base set of views, compile them into a DLL and re-use it across clients. So far I have that working well using RazorGenerator.
Now the next step is to allow for customization (overrides) of views. There is a caveat though. Our application has two "modes" that a user is in. The mode they are in could require a different view.
I have created a derived class from the RazorGeneratorView. This view basically inspects the "OrderingMode" from a UserProfile object that Autofac resolves. Based on the mode - the Path Locator is replaced for the view resolution.
The idea being individual client applications will attempt to resolve the view first in the traditional Views folder. Only I am adding in a sub-directory of Views/{OrderingMode}/{Controller}/{View}.cshtml.
If the view is not found - then it will look in the compiled library (the core views).
This allows me to override individual views / partials as need be for clients.
public PosViewEngine() : base()
{
//{0} = View Name
//{1} = ControllerName
//{2} = Area Name
AreaViewLocationFormats = new[]
{
//First look in the hosting application area folder / Views / ordering type
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"Areas/{2}/Views/%1/{1}/{0}.cshtml",
//Next look in the hosting application area folder / Views / ordering type / Shared
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"Areas/{2}/Views/%1/Shared/(0}.cshtml",
//Finally look in the IMS.POS.Web.Views.Core assembly
"Areas/{2}/Views/{1}/{0}.cshtml"
};
//Same format logic
AreaMasterLocationFormats = AreaViewLocationFormats;
AreaPartialViewLocationFormats = new[]
{
//First look in the hosting application area folder / Views / ordering type
//Areas/{AreaName}/{OrderType}/{ControllerName}/Partials/{PartialViewName}.cshtml
"Areas/{2}/Views/%1/{1}/Paritals/{0}.cshtml",
//Next look in the hosting application area folder / Views / ordering type / Shared
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"Areas/{2}/Views/%1/Shared/(0}.cshtml",
//Finally look in the IMS.POS.Web.Views.Core
"Areas/{2}/Views/{1}/{0}.cshtml"
};
ViewLocationFormats = new[]
{
"Views/%1/{1}/{0}.cshtml",
"Views/%1/Shared/{0}.cshtml",
"Views/{1}/{0}.cshtml",
"Views/Shared/{0}.cshtml"
};
MasterLocationFormats = ViewLocationFormats;
PartialViewLocationFormats = new[]
{
"Views/%1/{1}/Partials/{0}.cshtml",
"Views/%1/Shared/{0}.cshtml",
"Views/{1}/Partials/{0}.cshtml",
"Views/Shared/{0}.cshtml"
};
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return base.CreatePartialView(controllerContext, partialPath.ReplaceOrderType(CurrentOrderingMode()));
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
OrderType orderType = CurrentOrderingMode();
return base.CreateView(controllerContext, viewPath.ReplaceOrderType(orderType), masterPath.ReplaceOrderType(orderType));
}
protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
{
return base.FileExists(controllerContext, virtualPath.Replace("%1/",string.Empty));
}
private OrderType CurrentOrderingMode()
{
OrderType result;
_profileService = DependencyResolver.Current.GetService<IUserProfileService>();
if (_profileService == null || _profileService.OrderingType == 0)
{
IApplicationSettingService settingService =
DependencyResolver.Current.GetService<IApplicationSettingService>();
result =
settingService.GetApplicationSetting(ApplicationSettings.DefaultOrderingMode)
.ToEnumTypeOf<OrderType>();
}
else
{
result = _profileService.OrderingType;
}
return result;
}
}
Here is the StartUp class RazorGenerator uses to Register the ViewEngine.
public static class RazorGeneratorMvcStart
{
public static void Start()
{
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal
};
ViewEngines.Engines.Insert(0, engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
}
The problem is:
This code is executed last (after I register the PosViewEngine) and it inserts the engine at the first position (meaning this is the engine that gets resolved 1st when serving up responses). This ends up finding a view - it is the core view.
If I change the code in the StartUp to Register my custom view engine first first and then the RazorGenerator engine
public static void Start()
{
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal
};
ViewEngines.Engines.Clear();
ViewEngines.Engines.Insert(0, new PosViewEngine());
ViewEngines.Engines.Insert(1, engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
I end up with an exception on the FileExists(ControllerContext controllerContext, string virtualPath) method - "The relative virtual path 'Views/Account/LogOn.cshtml' is not allowed here."
It obviously has something to do with both physical and virtual paths being mixed together.
It looks like someone else was trying to do the same thing here but I didn't see an answer on this.
For anyone else wanting to try this approach I'll post the answer. Basically you need to implement a custom view engine that derives from the PrecompiledMvcEngine found in the RazorGenerator assembly.
public class PosPrecompileEngine : PrecompiledMvcEngine
{
private IUserProfileService _profileService;
public PosPrecompileEngine(Assembly assembly) : base(assembly)
{
LocatorConfig();
}
public PosPrecompileEngine(Assembly assembly, string baseVirtualPath) : base(assembly, baseVirtualPath)
{
LocatorConfig();
}
public PosPrecompileEngine(Assembly assembly, string baseVirtualPath, IViewPageActivator viewPageActivator) : base(assembly, baseVirtualPath, viewPageActivator)
{
LocatorConfig();
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return base.CreatePartialView(controllerContext, partialPath.ReplaceOrderType(CurrentOrderingMode()));
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
OrderType orderType = CurrentOrderingMode();
return base.CreateView(controllerContext, viewPath.ReplaceOrderType(orderType), masterPath.ReplaceOrderType(orderType));
}
protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
{
return base.FileExists(controllerContext, virtualPath.ReplaceOrderType(CurrentOrderingMode()));
}
}
In this class - I override the Locator Paths. Because I have the "base" compiled views in another assembly from the web application - we implemented a convention where the view engine will first look in a PosViews/{ordering mode}/{controller}/{view} path in the web application. If a view is not located -then it will look in the traditional /Views/controller/view. The trick here is the later is a virtual path located in another class library.
This allowed us to "override" an existing view for the application.
private void LocatorConfig()
{
//{0} = View Name
//{1} = ControllerName
//{2} = Area Name
AreaViewLocationFormats = new[]
{
//First look in the hosting application area folder / Views / ordering type
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"PosAreas/{2}/Views/%1/{1}/{0}.cshtml",
//Next look in the hosting application area folder / Views / ordering type / Shared
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"PosAreas/{2}/Views/%1/Shared/(0}.cshtml",
//Next look in the POS Areas Shared
"PosAreas/{2}/Views/Shared/(0}.cshtml",
//Finally look in the IMS.POS.Web.Views.Core assembly
"Areas/{2}/Views/{1}/{0}.cshtml"
};
//Same format logic
AreaMasterLocationFormats = AreaViewLocationFormats;
AreaPartialViewLocationFormats = new[]
{
//First look in the hosting application area folder / Views / ordering type
//Areas/{AreaName}/{OrderType}/{ControllerName}/Partials/{PartialViewName}.cshtml
"PosAreas/{2}/Views/%1/{1}/Partials/{0}.cshtml",
//Next look in the hosting application area folder / Views / ordering type / Shared
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"PosAreas/{2}/Views/%1/Shared/(0}.cshtml",
//Next look in the hosting application shared folder
"PosAreas/{2}/Views/Shared/(0}.cshtml",
//Finally look in the IMS.POS.Web.Views.Core
"Areas/{2}/Views/{1}/{0}.cshtml"
};
ViewLocationFormats = new[]
{
"~/PosViews/%1/{1}/{0}.cshtml",
"~/PosViews/%1/Shared/{0}.cshtml",
"~/PosViews/Shared/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
};
MasterLocationFormats = ViewLocationFormats;
PartialViewLocationFormats = new[]
{
"~/PosViews/%1/{1}/{0}.cshtml",
"~/PosViews/%1/Shared/{0}.cshtml",
"~/PosViews/Shared/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
};
}
Register this engine in your application start up events.
public static void Configure()
{
var engine = new PosPrecompileEngine(typeof(ViewEngineConfig).Assembly)
{
UsePhysicalViewsIfNewer = true,
PreemptPhysicalFiles = true
};
ViewEngines.Engines.Add(engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
Here is the final key. When RazorGenerator gets installed view NuGet - you end up with this start-up class that will run on startup
[assembly: WebActivatorEx.PostApplicationStartMethod(typeof(Views.Core.RazorGeneratorMvcStart), "Start")]
public static class RazorGeneratorMvcStart
{
public static void Start()
{
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = true,
PreemptPhysicalFiles = true
};
ViewEngines.Engines.Add(engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
}
By default - RazorGenerator adds ViewEngine to the first in the collection
ViewEngines.Engines.Insert(0,engine);
You need to change that to an add
ViewEngines.Engines.Add(engine);
So it is added to engines last - this way your custom ViewEngine is used FIRST in locating views.
This approach allows you to reuse views in multiple applications while allowing a means to override that view.
This may be overkill for most applications - bust as I mentioned in the question - this is base product that we use to develop multiple client applications. Trying achieve reuse while maintaining a level of flexibility on a per client basis is something we were trying to achieve.
My current set up:
Xamarin Forms, consisting of iOS, Android, WP app and shared PCL.
Using MVVM Light to keep a nice separation of concerns.
Brief intro into what I want to achieve. I want to have a Base page that has a Cancel and Next button. On pressing the Next button Content is loaded dynamically within that base page.
Xaml View:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="LogInPresenterView">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Cancel" Priority="0" Order="Primary" Command="{Binding Cancel}"></ToolbarItem>
<ToolbarItem Text="Next" Priority="1" Order="Primary" Command="{Binding Next}"></ToolbarItem>
</ContentPage.ToolbarItems>
</ContentPage>
ViewModel Code:
public class LogInPresenterViewModel : ViewModelBase
{
public LogInPresenterViewModel() {}
private RelayCommand _next;
public RelayCommand Next
{
get
{
return _next ?? (_next = new RelayCommand(async () => await DoNext()));
}
}
private async Task DoNext()
{
// IN HERE I WOULD LIKE TO DYNCAMICALLY LOAD CONTENT / VIEWS
}
}
Usually you would have a StackLayout etc before the element. However, on clicking the Next Toolbar Item I want to dynamically load content (that has a viewmodel).
So maybe my ICommand for my next button checked to see what the current content type was, and depending on that I would load another bit of content.
The scenario would be, the base page would load along with the first bit of content - Enter Email and Password. User enters that then clicks on next, if all ok, the content is replaced with the option to enter a security code, keeping the base Close and Next buttons at the top.
Hopefully this makes sense. I know what I want to do in my head, I just don't know how to translate that into Xamarin Forms...
Ok,
So first job is to create your region service in your PCL. This will look something like this:
using System;
using System.Collections.Generic;
namespace xxx
{
public class RegionService : IRegionService
{
private Dictionary<string, object> _regionDictionary;
public RegionService ()
{
_regionDictionary = new Dictionary<string, object> ();
}
#region IRegionService implementation
public bool RegisterRegion (string regionName, object regionObject)
{
object region = null;
_regionDictionary.TryGetValue (regionName, out region);
if (region != null)
_regionDictionary [regionName] = regionObject;
else
_regionDictionary.Add (regionName, regionObject);
return true;
}
public object ResolveRegion (string regionName)
{
object region = null;
_regionDictionary.TryGetValue (regionName, out region);
if (region == null)
throw new RegionServiceException ("Unable to resolve region with given name");
return region;
}
#endregion
}
}
This when you create your page with the dynamic content register your dynamic contentview in your code behind:
ContentView contentView = this.FindById<ContentView>("myContentView");
regionService.RegisterRegion("DynamicView", contentView);
You'll need to create an interface for your views and pages to use to indicate which region they wish to be presented in:
using System;
namespace xxx
{
public interface IRegionView
{
string GetRegionName ();
}
}
Then in your code behind for your view implement this interface to return the name of the region to display in.
You now need a custom presenter to use this region code. I use MVVMCross, so the details will vary for the MVVM implementation you are using, but essentially something like this is what you need:
public async static Task PresentPage(Page page)
{
if (typeof(IRegionView).GetTypeInfo().IsAssignableFrom(page.GetType().GetTypeInfo()))
{
IRegionService regionService = Mvx.Resolve<IRegionService>();
string regionName = (page as IRegionView).GetRegionName();
Page region = regionService.ResolveRegion(regionName) as Page;
if (typeof(IModalPage).GetTypeInfo().IsAssignableFrom(page.GetType().GetTypeInfo()))
await region.Navigation.PushModalAsync(page);
else if (typeof(IPopupPage).GetTypeInfo().IsAssignableFrom(page.GetType().GetTypeInfo()))
region.PushOverlayPage(page);
else if (typeof(NavigationPage).GetTypeInfo().IsAssignableFrom(region.GetType().GetTypeInfo()))
await (region as NavigationPage).PushAsync(page);
}
}
I hope this is useful for you :)
So if this was me. I would create a region service where the contentview registers a unique region name.
Content would then be marked to use that region, and a custom presenter can be used to show the view model's content in the appropriate region.
I'm on my phone whilst travelling at the moment but I can post some code later on if that helps :)
Tristan
You can dynamically load Xamarin Forms UI with XAML.
Old Answer:
This can be achieved with the use of the LoadFromXaml method. It works in the same was as XamlReader.Load in Silverlight/WPF. It is a hidden method that can be only accessed through reflection. There is an article on how to do it here:
http://www.cazzulino.com/dynamic-forms.html
But, I would like to ask to you go to this feature request at Xamarin and ask that the method be made public so that it becomes a fully supported feature:
https://forums.xamarin.com/discussion/comment/252626
In my MVC 4 Controller, I want to override the View() method
ViewResult View(string viewName, string masterName, object model) {}
So that I can manipulate the view being rendered by the action method. To do this, I want to be able to obtain the physical path of the view file. I have tried the following:
string viewName = this.ControllerContext.RouteData.Route
.GetVirtualPath(this.ControllerContext.RequestContext, null)
.VirtualPath;
For example, this might return "/Errors/MissingParameters" when what I really want it to return is something like:
"~/Views/Errors/MissingParameters"
or, even better:
"~/Views/Errors/MissingParameters.cshtml"
Just to add complication, I also need it to cope with Areas, so if I had the same example running in an Area named "Surveys", I would want it to return something like:
"~/Areas/Surveys/Views/Errors/MissingParameters"
The reason I want to do this is that I'm experimenting with using views for globalization, so I might have two views:
"~/Views/Errors/MissingParameters.cshtml" // default view (en-GB)
"~/Views/Errors/MissingParameters_de-DE.cshtml" // German view (de-DE)
and I want to be able to check if the view exists for the current language/culture before referencing it.
Any advice would be much appreciated.
Thanks.
EDIT: This part will not work or is hard to implement
You'd rather use an action filter which will let you manipulate the Result before executing it.
Particularly you need a Result filter. Implement the IResultFilter.onResultExecuting method, and change the result there. Particularly when you implement this method:
void OnResultExecuting(ResultExecutingContext filterContext)
You can access the ResultExecutingContext.Result Property. This property will contain your view. If you cast it to System.Web.Mvc.ViewResultBase you'll have access to the ViewName and you'll be able to change it.
If you've never implemented a filter, this is a good hands-on-lab on the subject. In this case it implements another kind of filter, but it's just the same.
As an answer to the OP comment, it's perfectly normal that ViewName is missing, and View is still null. ViewName wouldn't be empty only if the case that the view is returned with name, like this: return View("Index");. And, the ViewName would be just, not the whole path to the view. So this is not a solution. So, to have this solution working you would have to deal with route data, controller context, etc. to find the view. (More on this below.)
EDIT: Solution, register a custom view engine
When MVC has to render a view it gets the information from the route data, the controller context, the view name (that, as explained above can be empty), and the conventions that apply.
Particularly, in MVC there is a collection of registered view engines which are required to find the view calling there FindView() method. The view engine will return a ViewEngineResult which has the found view, if one was found, or a list of the paths where the view has been unsuccesfully sought.
So, to modify the template path, you can override this funcionality: let the original class find the view, and, if it is found, modify the path.
To do show you need to take theses steps:
Inherit the view engine which you're using (my sampel code inherits Razor view engine)
Register your vie engine, so that it's queried before the original view engine (in my sample code I simply clear the registered engines list, and register mine. The original list includes razor and web form view engines)
This is the code for the inherited view engine:
public class CustomRazorViewEngine : FixedRazorViewEngine
{
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
ViewEngineResult result
= base.FindView(controllerContext, viewName, masterName, useCache);
if (result.View != null)
{
// Modify here !!
}
return result;
}
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
ViewEngineResult result
= base.FindPartialView(controllerContext, partialViewName, useCache);
if (result.View != null)
{
// Modify here !!
}
return result;
}
static readonly PropertyInfo ViewPathProp
= typeof(RazorView).GetProperty("ViewPath");
public void SetViewPath(RazorView view, string path)
{
ViewPathProp.SetValue(view, path);
}
}
NOTE 1: where you read // Modify here !! you can modify the path property of the result.View. Cast it to RazorView: (result.View as RazorView).ViewPath. As the ViewPath setter is protected, you need to set it using Reflection: you can use the SetViewPath method for this.
NOTE 2: As you can see I'm not inheriting the RazorViewEngine but the FixedRazorViewEngine. If you loook for this class in MSDN you'll get not results, but if you look the original content of the registered view engines list, you'll find this class. I think this depends on an installed package in the project, and I think it solves a bug in MVC4. If you don't finf it in Microsoft.Web.Mvc namespace, inherit the original RazorViewEngined
NOTE 3: after the view is found, the view engine executes it, using the ViewEngineResult, so, if you change it, it will be executed with the new view path
And finally, you need to change the list of registered engines, in global.asax application start event, like this:
protected void Application_Start()
{
// Original content:
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// Added content:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomRazorViewEngine());
}
NOTE: it would be cleaner, if you created a ViewEngineConfig class in App_Start folder, and invoked an static method of this class, just as it's done with all other configurations.
Answer was copied from here.
Well if you don't mind having your code tied to the specific view engine you're using, you can look at the ViewContext.View property and cast it to WebFormView
var viewPath = ((WebFormView)ViewContext.View).ViewPath;
I believe that will get you the view name at the end.
EDIT: Haacked is absolutely spot-on; to make things a bit neater I've wrapped the logic up in an extension method like so:
public static class IViewExtensions {
public static string GetWebFormViewName(this IView view) {
if (view is WebFormView) {
string viewUrl = ((WebFormView)view).ViewPath;
string viewFileName = viewUrl.Substring(viewUrl.LastIndexOf('/'));
string viewFileNameWithoutExtension = Path.GetFileNameWithoutExtension(viewFileName);
return (viewFileNameWithoutExtension);
} else {
throw (new InvalidOperationException("This view is not a WebFormView"));
}
}
}
which seems to do exactly what I was after.
Another solution here
((System.Web.Mvc.RazorView)htmlHelper.ViewContext.View).ViewPath
net-mvc
i am registering the area but i don't want my
url: "{area}/{controller}/{action}/{id}",
instead i want it to be like
url: "{controller}/{action}/{id}",
so i have registered my area like
context.MapRoute(
name: "AreaName_default",
url: "{controller}/{action}/{id}",
namespaces: new[] { "SolutionName.AreaName.Controllers" }
);
and i don't want to add the hard code string viewpath while returning view in every action method like
return View("~/Areas/AreaName/Views/ControllerName/ViewName.cshtml", model);
so i have created one result filter and override OnResultExecuting function
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
string areaName = AreaNameAreaRegistration.PropoertyName;
if (filterContext.Result.GetType() == typeof(ViewResult) || filterContext.Result.GetType() == typeof(PartialViewResult))
{
dynamic viewResult = filterContext.Result;
string viewname = string.IsNullOrEmpty(viewResult.ViewName) ? Convert.ToString(filterContext.RouteData.Values["action"]) : viewResult.ViewName;
string folder = Convert.ToString(filterContext.RouteData.Values["controller"]);
string lateralHireAreaViewPath = $"~/Areas/{areaName}/Views/";
string extension = viewname.Contains(".cshtml") ? "" : ".cshtml";
viewResult.ViewName = string.Concat(lateralHireAreaViewPath, folder, "/", viewname, extension);
ViewEngineResult result = ViewEngines.Engines.FindView(filterContext.Controller.ControllerContext, viewResult.ViewName, null);
if (result.View == null)
{
//searched in shared folder
lateralHireAreaViewPath = string.Concat(lateralHireAreaViewPath, "Shared/");
viewResult.ViewName = string.Concat(lateralHireAreaViewPath, "/", viewname, extension);
}
}
}
This is for my Windows 8 app:
In my object I have a string property that contains the path of the images I want to use.
public String ImagePath
In my XAML I have set up an Image tag with the following binding:
<Image Source="{Binding ImagePath}" Margin="50"/>
When I reference an image that I've included in my project (in the Asset folder) the image displays correctly. The path is: Assets/car2.png
However, when I reference an image that the user selects (using the FilePicker) I get an error (and no image). The path is: C:\Users\Jeff\Pictures\myImage.PNG
Converter failed to convert value of type 'Windows.Foundation.String'
to type 'ImageSource'
Just to add a little more info. When I use the file picker I am converting the file location to a URI:
Uri uriAddress = new Uri(file.Path.ToString());
_VM.vehicleSingle.ImagePath = uriAddress.LocalPath;
Update:
I'm also saving this image path to isolated storage. I think this is where the issue is. I'm able to save the path of the file selected, but when I try to bind to it when I'm reloading the Isolated Storage it doesn't work.
So if I can't use an image outside of the application directory. Is there a way I can save that image and add it to the directory?
I tried creating a BitmapImage property to my model but now I'm getting errors stating that it can't serialize a BitmapImage.
You should Use Converter
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
MemoryStream memStream = new MemoryStream((byte[])value,false);
BitmapImage empImage = new BitmapImage();
empImage.SetSource(memStream);
return empImage;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can't use a file path that points outside the app directory. You will need to read in the StorageFile stream that you get from the file picker and assign that stream to an image source - so binding is pretty hard unless you change your model,to have an imagesource property instead.
As mentioned, you cannot use bindings to access the file system directly, even if you grant access via the File Picker. Take a look at the XAML Images Sample at the Dev Center, for a technique you can use.
In a nutshell, you'll use SetSourceAsync to get your file into a BitmapImage and then you can use that as the binding source.
I recently did some work on binding to an ImageSource.
public System.Windows.Media.ImageSource PhotoImageSource
{
get
{
if (Photo != null)
{
System.Windows.Media.Imaging.BitmapImage image = new System.Windows.Media.Imaging.BitmapImage();
image.BeginInit();
image.StreamSource = new MemoryStream(Photo);
image.EndInit();
return image as System.Windows.Media.ImageSource;
}
else
{
return null;
}
}
}
My "Photo" was an image stored in a byte[]. You could either convert your image to a byte[] or maybe try using a FileStream instead (I haven't tested with a FileStream so I can't say if it will work).
Resource files in Silverlight can be accessed using the code below:
ResourceManager rm = new ResourceManager("MyLibraryNamespace.MyFolder.MyResources", Assembly.GetExecutingAssembly());
However in my application this piece of code is not in the application itself, but in a Silverlight class library and the app has reference to it; changing the namespace to the "MyAppNamespace" just generates error.
How can I reach the resources in the xap file from the Silverlight class library?
There is a nice video here: http://msdn.microsoft.com/en-us/hh336287
The trick is to write a "proxy" class so that you can reference strings from XAML. From MSDN:
public class LocalizedStrings {
public LocalizedStrings() { }
private static sdkGlobalizationCS.AppResources localizedResources = new sdkGlobalizationCS.AppResources();
public sdkGlobalizationCS.AppResources LocalizedResources { get { return localizedResources; } }
}
And in XAML (after adding the class in the static resources):
<ListBoxItem Content="{Binding Path=LocalizedResources.LangRegionNameFrFR, Source={StaticResource LocalizedStrings}}" />
This is good and I was able to do the same.
In my case I have the same library shared between applications so I extract dynamically the name of the assembly:
var ast = assembly.FullName;
char[] delimit = new char[] { ',' };
string[] parts = ast.Split(delimit);
var gResources = new System.Resources.ResourceManager(parts[0]+"resource path here", assembly);
In order to achieve what I wanted I had to do the following:
var assembly = Application.Current.GetType().Assembly;
And after that I can create ResourceManager with the resources like this:
var rm = new System.Resources.ResourceManager(name, assembly);
where name is the path from my first post.