I want to create and AMP version of my website in ASP.NET MVC using .NET Core 2.0. Previously I had done some work with DisplayModeProvider instances in tha past on .Net framework, but that does not seem to be an option in .NET Core.
What I want to be able to do is alter the view names to be index.amp.cshtml rather than index.cshtml when my URL starts iwth /amp. What's the best way to achieve this in .NET Core?
You can do something like this using IViewLocationExpander. As it happens, I was playing with this a few days ago so I have some code samples to hand. If you create something like this:
public class AmpViewLocationExpander : IViewLocationExpander
{
public void PopulateValues(ViewLocationExpanderContext context)
{
var contains = context.ActionContext.HttpContext.Request.Query.ContainsKey("amp");
context.Values.Add("AmpKey", contains.ToString());
var containsStem = context.ActionContext.HttpContext.Request.Path.StartsWithSegments("/amp");
context.Values.Add("AmpStem", containsStem.ToString());
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
if (!(context.ActionContext.ActionDescriptor is ControllerActionDescriptor descriptor)) { return viewLocations; }
if (context.ActionContext.HttpContext.Request.Query.ContainsKey("amp")
|| context.ActionContext.HttpContext.Request.Path.StartsWithSegments("/amp")
)
{
return viewLocations.Select(x => x.Replace("{0}", "{0}.amp"));
}
return viewLocations;
}
}
iViewLocationExpander can be found in Microsoft.AspNetCore.Mvc.Razor
Then in your Configure Services method in Startup.cs, add the following:
services.Configure<RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new AmpViewLocationExtender());
});
What this will do is update the view locations per request to insert .amp before .cshtml any time the URL either starts with /amp or there is a query string key of amp. If your AMP views don't exist, it might blow-up a little, I've not fully tested it, but it should get you started.
You can define this Middleware :
public class AmpMiddleware
{
private RequestDelegate _next;
public AmpMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
const string ampTag = "/amp";
var path = context.Request.Path;
if (path.HasValue)
{
var ampPos = path.Value.IndexOf(ampTag);
if (ampPos >= 0)
{
context.Request.Path = new PathString(path.Value.Remove(ampPos, ampTag.Length));
context.Items.Add("amp", "true");
}
}
return _next(context);
}
}
public static class BuilderExtensions
{
public static IApplicationBuilder UseAmpMiddleware(this IApplicationBuilder app)
{
return app.UseMiddleware<AmpMiddleware>();
}
}
And call it in Startup:
app.UseAmpMiddleware();
Then can check in page and simple set another layout or limit some code, in his way no need to create separate page for amp version:
#if (HttpContext.Items.ContainsKey("amp"))
{
<b>Request AMP Version</b>
}
Related
I have Xamarin Forms solution and in Xamarin Droid project I have custom renderer for class CustomCameraScanView that extends ContentView. This CustomCameraScanView should use Camera (old API) on Androids before 5.0 (Lollipop) and Camera2 (new API) from 5.0 and afterwards. How can I create two renderers: CustomCameraScanViewRenderer_Droid4 and CustomCameraScanViewRenderer_Droid5 that will be used on different versions of OS?
My CustomCameraScanViewRenderer_Droid4 looks like this:
[assembly: ExportRenderer(typeof(CustomCameraScanView), typeof(CustomCameraScanViewRenderer_Droid4))]
namespace Genea.Droid.Renderers
{
public class CustomCameraScanViewRenderer_Droid4 : ViewRenderer,
ISurfaceTextureListener,
ISurfaceHolderCallback,
Android.Hardware.Camera.IPreviewCallback,
Android.Hardware.Camera.IPictureCallback,
Android.Hardware.Camera.IShutterCallback
{
}
}
Unfortunately, registering a custom-renderer at runtime is not possible at this point in Xamarin-Forms. More details can be found on this forum link. But what you can do is resolve the controls at run-time using OnPlatform and build-version number.
I would of course recommend the conditional-compilation approach first as suggested by #luccas-clezar; but if that is not an option, then you can try resolving your control (and hence the renderer) at run-time using following steps.
Steps:
Create an simple contract to resolve current API value - in forms project
public interface IBuildVersion { int Number { get; } }
Implement it for Android platform
public class AndroidBuildVersion : IBuildVersion
{
public int Number { get { return ((int)Android.OS.Build.VERSION.SdkInt); } }
}
Register on DependencyService
[assembly: Xamarin.Forms.Dependency (typeof (AndroidBuildVersion))]
Subclass your CustomCameraScanView into two derived types - in forms project
public class CustomCameraScanView_Droid4 : CustomCameraScanView { }
public class CustomCameraScanView_Droid5 : CustomCameraScanView { }
Setup your renderers in Android project
[assembly: ExportRenderer(typeof(CustomCameraScanView_Droid4), typeof(CustomCameraScanViewRenderer_Droid4))]
and,
[assembly: ExportRenderer(typeof(CustomCameraScanView_Droid5), typeof(CustomCameraScanViewRenderer_Droid5))]
Create custom-control to resolve at run-time - using OnPlatform - in forms project
public class CameraScanContentView : Xamarin.Forms.ContentView
{
public CameraScanContentView()
{
this.Content = Device.OnPlatform(
iOS: new CustomCameraScanView(),
Android: DependencyService.Get<IBuildVersion>().Number < 21
? ((CustomCameraScanView)new CustomCameraScanView_Droid4())
: ((CustomCameraScanView)new CustomCameraScanView_Droid5()),
WinPhone: new CustomCameraScanView()
);
}
}
And, XAML usage will look like this
<local:CameraScanContentView />
Update 05/24
I don't believe Xamarin-Forms supports 'Property Value' inheritance as WPF does - so it is kind of tricky to propagate the values from parent to child control.
One option is to cascade these values back to CustomCameraScanView from CameraScanContentView by defining bindable-properties (recommended):
public class CameraScanContentView : Xamarin.Forms.ContentView
{
public CameraScanContentView()
{
CustomCameraScanView scannerCtrl = null;
switch (Device.RuntimePlatform)
{
case Device.Android:
scannerCtrl = DependencyService.Get<IBuildVersion>().Number < 21
? ((CustomCameraScanView)new CustomCameraScanView_Droid4())
: ((CustomCameraScanView)new CustomCameraScanView_Droid5());
break;
default:
scannerCtrl = new CustomCameraScanView();
break;
}
scannerCtrl.SetBinding(CustomCameraScanView.IsScanningProperty, new Binding(nameof(IsScanning), source: this));
scannerCtrl.SetBinding(CustomCameraScanView.IsTakingImageProperty, new Binding(nameof(IsTakingImage), source: this));
scannerCtrl.SetBinding(CustomCameraScanView.IsFlashlightOnProperty, new Binding(nameof(IsFlashlightOn), source: this));
Content = scannerCtrl;
}
public static readonly BindableProperty IsScanningProperty = BindableProperty.Create("IsScanning", typeof(bool), typeof(CameraScanContentView), false);
public static readonly BindableProperty IsTakingImageProperty = BindableProperty.Create("IsTakingImage", typeof(bool), typeof(CameraScanContentView), false);
public static readonly BindableProperty IsFlashlightOnProperty = BindableProperty.Create("IsFlashlightOn", typeof(bool), typeof(CameraScanContentView), false);
public bool IsScanning
{
get { return (bool)GetValue(IsScanningProperty); }
set { SetValue(IsScanningProperty, value); }
}
public bool IsTakingImage
{
get { return (bool)GetValue(IsTakingImageProperty); }
set { SetValue(IsTakingImageProperty, value); }
}
public bool IsFlashlightOn
{
get { return (bool)GetValue(IsFlashlightOnProperty); }
set { SetValue(IsFlashlightOnProperty, value); }
}
}
XAML usage will then look like this
<local:CameraScanContentView IsScanning="{Binding IsScanning}" IsFlashlightOn="{Binding IsFlashlightOn}" IsTakingImage="{Binding IsTakingImage}" />
Or, you can manually define your binding-expressions as below (not recommended as it makes the assumption that the property-names on view-model will not change):
public class CameraScanContentView : Xamarin.Forms.ContentView
{
public CameraScanContentView()
{
CustomCameraScanView scannerCtrl = null;
switch (Device.RuntimePlatform)
{
case Device.Android:
scannerCtrl = DependencyService.Get<IBuildVersion>().Number < 21
? ((CustomCameraScanView)new CustomCameraScanView_Droid4())
: ((CustomCameraScanView)new CustomCameraScanView_Droid5());
break;
default:
scannerCtrl = new CustomCameraScanView();
break;
}
scannerCtrl.SetBinding(CustomCameraScanView.IsScanningProperty, new Binding("IsScanning", source: this.BindingContext));
scannerCtrl.SetBinding(CustomCameraScanView.IsTakingImageProperty, new Binding("IsTakingImage", source: this.BindingContext));
scannerCtrl.SetBinding(CustomCameraScanView.IsFlashlightOnProperty, new Binding("IsFlashlightOn", source: this.BindingContext));
Content = scannerCtrl;
}
}
Note: My first instinct was to use an implicit style targeting CustomCameraScanView to bind the values - but somehow couldn't get it to work.
There's no need to have two renderers I think. Something like this should work:
if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop)
{
// Access old API
}
else
{
// Access new API
}
Another method of doing the same thing is by adding compiler directives. Like so:
#if __ANDROID_21__
// New API (>=21)
#else
// Old API (<21)
#endif
This will compile only the specific code of that Android API. I'm not sure if this works with PCL projects and the only thing that I could find that said something about it is this line from Dealing with Multiple Platforms guide: "Conditional compilation works best with Shared Asset Projects, where the same source file is being referenced in multiple projects that have different symbols defined.". Anyway, I think you could give it a try.
Hope it helps! :)
You will probably have to put some code like the code below inside the renderers so their methods don't get called unless a certain platform requirement is met:
if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)
{
// Lollipop specific stuff
}
There is no mechanism in place to actually point to a different renderer on different OS versions.
You can put the following code in your main activity right after Forms.Init() (if you are using forms)
if (Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
Xamarin.Forms.Internals.Registrar.Registered.Register(typeof(Xamarin.Forms.Picker), typeof(PickerRendererEx));
}
else
{
Xamarin.Forms.Internals.Registrar.Registered.Register(typeof(Xamarin.Forms.Picker), typeof(PickerRendererExOld));
}
I just upgraded a project from an older version of MvvmCross to the latest version, and I'm having trouble with bindings.
I'm aware of the LinkerPleaseInclude hack, and it looks like (at least some of) the properties I'm using are listed there.
I'm concerned about my usage of the Views and ViewModels. Here's an example.
public partial class HomeView : MvxViewController<HomeViewModel>
...
public override void ViewDidLoad ()
{
base.ViewDidLoad();
this.CreateBinding(BudgetTrackerButton).To((HomeViewModel vm) => vm.BudgetTracker).Apply();
this.CreateBinding(LoginButton).For("Title").To ((HomeViewModel vm) => vm.LogMessage).Apply();
this.CreateBinding(LoginButton).To((HomeViewModel vm) => vm.Login).Apply();
this.CreateBinding(ContactApprisenButton).To((HomeViewModel vm) => vm.Contact).Apply();
this.CreateBinding(AboutApprisenButton).To((HomeViewModel vm) => vm.About).Apply();
this.CreateBinding(AboutThisAppButton).To((HomeViewModel vm) => vm.AboutApp).Apply();
this.CreateBinding(FAQsButton).To((HomeViewModel vm) => vm.FAQs).Apply();
this.CreateBinding(PrivacyPolicyButton).To((HomeViewModel vm) => vm.PrivacyPolicy).Apply();
}
public abstract class MvxViewController<T> : UIViewController, IMvxBindingContextOwner, IUIWrappable, MvvmCross.iOS.Views.IMvxIosView where T : ViewModelBase
...
}
protected MvvmCross.Core.ViewModels.IMvxViewModel _viewModel = null;
public MvvmCross.Core.ViewModels.IMvxViewModel ViewModel {
get {
if (_viewModel == null) {
_viewModel = MvvmCross.Platform.Mvx.Resolve<T> ();
}
return _viewModel;
}
set {
_viewModel = value;
}
}
public MvvmCross.Core.ViewModels.MvxViewModelRequest Request {
get;
set;
}
public object DataContext {
get {
return _viewModel;
}
set {
_viewModel = (MvvmCross.Core.ViewModels.IMvxViewModel)value;
}
}
protected MvvmCross.Binding.BindingContext.IMvxBindingContext _bindingContext;
public IMvxBindingContext BindingContext {
get {
if (_bindingContext == null) {
_bindingContext = new MvvmCross.Binding.BindingContext.MvxBindingContext ();
}
return _bindingContext;
}
set {
_bindingContext = value;
}
}
CreateBinding is from the MvxBindingContextOwnerExtensions.
When I hit CreateBinding, the view model has been made.
I understand DataContext to be the same as the ViewModel, and I only include it to conform to the MvvmCross.iOS.Views.IMvxIosView interface.
Am I missing a step somewhere? An interface?
Matching views to view models should happen automatically based on naming conventions, right? (They didn't.. in my case, I had to manually specify the mappings in my Setup class. Could be contributing to this problem.)
Strangely enough, this works (for the button title anyway, I haven't tested the other bindings, and I'm not interested in updating all the bindings across the entire application if the fix is simple):
var set = this.CreateBindingSet<HomeView, HomeViewModel>();
set.Bind(LoginButton).For("Title").To(vm => vm.LogMessage);
I can post more code if something else would be relevant. I'm also new to MvvmCross.
I have the following code in my Android app, it basically uses one page (using a NavigationDrawer) and swaps fragments in/out of the central view. This allows the navigation to occur on one page instead of many pages:
Setup.cs:
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
var customPresenter = new MvxFragmentsPresenter();
Mvx.RegisterSingleton<IMvxFragmentsPresenter>(customPresenter);
return customPresenter;
}
ShellPage.cs
public class ShellPage : MvxCachingFragmentCompatActivity<ShellPageViewModel>, IMvxFragmentHost
{
.
.
.
public bool Show(MvxViewModelRequest request, Bundle bundle)
{
if (request.ViewModelType == typeof(MenuContentViewModel))
{
ShowFragment(request.ViewModelType.Name, Resource.Id.navigation_frame, bundle);
return true;
}
else
{
ShowFragment(request.ViewModelType.Name, Resource.Id.content_frame, bundle, true);
return true;
}
}
public bool Close(IMvxViewModel viewModel)
{
CloseFragment(viewModel.GetType().Name, Resource.Id.content_frame);
return true;
}
.
.
.
}
How can I achieve the same behavior in a Windows UWP app? Or rather, is there ANY example that exists for a Windows MvvmCross app which implements a CustomPresenter? That may at least give me a start as to how to implement it.
Thanks!
UPDATE:
I'm finally starting to figure out how to go about this with a customer presenter:
public class CustomPresenter : IMvxWindowsViewPresenter
{
IMvxWindowsFrame _rootFrame;
public CustomPresenter(IMvxWindowsFrame rootFrame)
{
_rootFrame = rootFrame;
}
public void AddPresentationHintHandler<THint>(Func<THint, bool> action) where THint : MvxPresentationHint
{
throw new NotImplementedException();
}
public void ChangePresentation(MvxPresentationHint hint)
{
throw new NotImplementedException();
}
public void Show(MvxViewModelRequest request)
{
if (request.ViewModelType == typeof(ShellPageViewModel))
{
//_rootFrame?.Navigate(typeof(ShellPage), null); // throws an exception
((Frame)_rootFrame.UnderlyingControl).Content = new ShellPage();
}
}
}
When I try to do a navigation to the ShellPage, it fails. So when I set the Content to the ShellPage it works, but the ShellPage's ViewModel is not initialized automatically when I do it that way. I'm guessing ViewModels are initialized in MvvmCross using OnNavigatedTo ???
I ran into the same issue, and built a custom presenter for UWP. It loans a couple of ideas from an Android sample I found somewhere, which uses fragments. The idea is as follows.
I have a container view which can contain multiple sub-views with their own ViewModels. So I want to be able to present multiple views within the container.
Note: I'm using MvvmCross 4.0.0-beta3
Presenter
using System;
using Cirrious.CrossCore;
using Cirrious.CrossCore.Exceptions;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Views;
using Cirrious.MvvmCross.WindowsUWP.Views;
using xxxxx.WinUniversal.Extensions;
namespace xxxxx.WinUniversal.Presenters
{
public class MvxWindowsMultiRegionViewPresenter
: MvxWindowsViewPresenter
{
private readonly IMvxWindowsFrame _rootFrame;
public MvxWindowsMultiRegionViewPresenter(IMvxWindowsFrame rootFrame)
: base(rootFrame)
{
_rootFrame = rootFrame;
}
public override async void Show(MvxViewModelRequest request)
{
var host = _rootFrame.Content as IMvxMultiRegionHost;
var view = CreateView(request);
if (host != null && view.HasRegionAttribute())
{
host.Show(view as MvxWindowsPage);
}
else
{
base.Show(request);
}
}
private static IMvxWindowsView CreateView(MvxViewModelRequest request)
{
var viewFinder = Mvx.Resolve<IMvxViewsContainer>();
var viewType = viewFinder.GetViewType(request.ViewModelType);
if (viewType == null)
throw new MvxException("View Type not found for " + request.ViewModelType);
// Create instance of view
var viewObject = Activator.CreateInstance(viewType);
if (viewObject == null)
throw new MvxException("View not loaded for " + viewType);
var view = viewObject as IMvxWindowsView;
if (view == null)
throw new MvxException("Loaded View is not a IMvxWindowsView " + viewType);
view.ViewModel = LoadViewModel(request);
return view;
}
private static IMvxViewModel LoadViewModel(MvxViewModelRequest request)
{
// Load the viewModel
var viewModelLoader = Mvx.Resolve<IMvxViewModelLoader>();
return viewModelLoader.LoadViewModel(request, null);
}
}
}
IMvxMultiRegionHost
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.WindowsUWP.Views;
namespace xxxxx.WinUniversal.Presenters
{
public interface IMvxMultiRegionHost
{
void Show(MvxWindowsPage view);
void CloseViewModel(IMvxViewModel viewModel);
void CloseAll();
}
}
RegionAttribute
using System;
namespace xxxxx.WinUniversal.Presenters
{
[AttributeUsage(AttributeTargets.Class)]
public sealed class RegionAttribute
: Attribute
{
public RegionAttribute(string regionName)
{
Name = regionName;
}
public string Name { get; private set; }
}
}
These are the three foundational classes you need. Next you'll need to implement the IMvxMultiRegionHost in a MvxWindowsPage derived class.
This is the one I'm using:
HomeView.xaml.cs
using System;
using System.Diagnostics;
using System.Linq;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.WindowsUWP.Views;
using xxxxx.Shared.Controls;
using xxxxx.WinUniversal.Extensions;
using xxxxx.WinUniversal.Presenters;
using xxxxx.Core.ViewModels;
namespace xxxxx.WinUniversal.Views
{
public partial class HomeView
: MvxWindowsPage
, IMvxMultiRegionHost
{
public HomeView()
{
InitializeComponent();
}
// ...
public void Show(MvxWindowsPage view)
{
if (!view.HasRegionAttribute())
throw new InvalidOperationException(
"View was expected to have a RegionAttribute, but none was specified.");
var regionName = view.GetRegionName();
RootSplitView.Content = view;
}
public void CloseViewModel(IMvxViewModel viewModel)
{
throw new NotImplementedException();
}
public void CloseAll()
{
throw new NotImplementedException();
}
}
}
The last piece to make this work is the way the actual xaml in the view is set-up. You'll notice that I'm using a SplitView control, and that I'm replacing the Content property with the new View that's coming in in the ShowView method on the HomeView class.
HomeView.xaml
<SplitView x:Name="RootSplitView"
DisplayMode="CompactInline"
IsPaneOpen="false"
CompactPaneLength="48"
OpenPaneLength="200">
<SplitView.Pane>
// Some ListView with menu items.
</SplitView.Pane>
<SplitView.Content>
// Initial content..
</SplitView.Content>
</SplitView>
EDIT:
Extension Methods
I forgot to post the two extension methods to determine if the view declares a [Region] attribute.
public static class RegionAttributeExtentionMethods
{
public static bool HasRegionAttribute(this IMvxWindowsView view)
{
var attributes = view
.GetType()
.GetCustomAttributes(typeof(RegionAttribute), true);
return attributes.Any();
}
public static string GetRegionName(this IMvxWindowsView view)
{
var attributes = view
.GetType()
.GetCustomAttributes(typeof(RegionAttribute), true);
if (!attributes.Any())
throw new InvalidOperationException("The IMvxView has no region attribute.");
return ((RegionAttribute)attributes.First()).Name;
}
}
Hope this helps.
As the link to the blog of #Stephanvs is no longer active I was able to pull the content off the Web Archive, i'll post it here for who ever is looking for it:
Implementing a Multi Region Presenter for Windows 10 UWP and MvvmCross
18 October 2015 on MvvmCross, Xamarin, UWP, Windows 10, Presenter > Universal Windows Platform
I'm upgrading a Windows Store app to the new Windows 10 Universal
Windows Platform. MvvmCross has added support for UWP in v4.0-beta2.
A new control in the UWP is the SplitView control. Basically it
functions as a container view which consist of two sub views, shown
side-by-side. Mostly it's used to implement the (in)famous hamburger
menu.
By default MvvmCross doesn't know how to deal with the SplitView, and
just replaces the entire screen contents with a new View when
navigating between ViewModels. If however we want to lay-out our views
differently and show multiple views within one window, we need a
different solution. Luckily we can plug-in a custom presenter, which
will take care of handling the lay-out per platform.
Registering the MultiRegionPresenter
In the Setup.cs file in your UWP project, you can override the
CreateViewPresenter method with the following implementation.
protected override IMvxWindowsViewPresenter CreateViewPresenter(IMvxWindowsFrame rootFrame)
{
return new MvxWindowsMultiRegionViewPresenter(rootFrame);
}
Using Regions
We can define a region by declaring a
element. At this point it has to be a Frame type because then we can
also show a nice transition animation when switching views.
<mvx:MvxWindowsPage ...>
<Grid>
<!-- ... -->
<SplitView>
<SplitView.Pane>
<!-- Menu Content as ListView or something similar -->
</SplitView.Pane>
<SplitView.Content>
<Frame x:Name="MainContent" />
</SplitView.Content>
</SplitView>
</Grid>
</mvx:MvxWindowsPage>
Now we want to be able when a ShowViewModel(...) occurs to swap out
the current view presented in the MainContent frame.
Showing Views in a Region
In the code-behind for a View we can now declare a MvxRegionAttribute,
defining in which region we want this View to be rendered. This name
has to match a Frame element in the view.
[MvxRegion("MainContent")]
public partial class PersonView
{
// ...
}
It's also possible to declare multiple regions within the same view.
This would allow you to split up your UI in more re-usable pieces.
Animating the Transition between Content Views
If you want a nice animation when transitioning between views in the
Frame, you can add the following snippet to the Frame declaration.
<Frame x:Name="MainContent">
<Frame.ContentTransitions>
<TransitionCollection>
<NavigationThemeTransition>
<NavigationThemeTransition.DefaultNavigationTransitionInfo>
<EntranceNavigationTransitionInfo />
</NavigationThemeTransition.DefaultNavigationTransitionInfo>
</NavigationThemeTransition>
</TransitionCollection>
</Frame.ContentTransitions>
</Frame>
The contents will now be nicely animated when navigating.
Hope this helps, Stephanvs
When I tried to use the Selfhosted WebAPI in LINQPad, I just kept getting the same error that a controller for the class didn't exist.
Do I have to create separate assemblies for the WebAPI (Controllers/Classes) and then reference them in my query?
Here's the code I'm using
#region namespaces
using AttributeRouting;
using AttributeRouting.Web.Http;
using AttributeRouting.Web.Http.SelfHost;
using System.Web.Http.SelfHost;
using System.Web.Http.Routing;
using System.Web.Http;
#endregion
public void Main()
{
var config = new HttpSelfHostConfiguration("http://192.168.0.196:8181/");
config.Routes.MapHttpAttributeRoutes(cfg =>
{
cfg.AddRoutesFromAssembly(Assembly.GetExecutingAssembly());
});
config.Routes.Cast<HttpRoute>().Dump();
AllObjects.Add(new UserQuery.PlayerObject { Type = 1, BaseAddress = "Hej" });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
using(HttpSelfHostServer server = new HttpSelfHostServer(config))
{
server.OpenAsync().Wait();
Console.WriteLine("Server open, press enter to quit");
Console.ReadLine();
server.CloseAsync();
}
}
public static List<PlayerObject> AllObjects = new List<PlayerObject>();
public class PlayerObject
{
public uint Type { get; set; }
public string BaseAddress { get; set; }
}
[RoutePrefix("players")]
public class PlayerObjectController : System.Web.Http.ApiController
{
[GET("allPlayers")]
public IEnumerable<PlayerObject> GetAllPlayerObjects()
{
var players = (from p in AllObjects
where p.Type == 1
select p);
return players.ToList();
}
}
This code works fine when in a separate Console Project in VS2012.
I started using AttributeRouting via NuGET when I didn't get the "normal" WebAPI-routing to work.
The error I got in the browser was: No HTTP resource was found that matches the request URI 'http://192.168.0.196:8181/players/allPlayers'.
Additional error: No type was found that matches the controller named 'PlayerObject'
Web API by default will ignore controllers that are not public, and LinqPad classes are nested public, we had similar problem in scriptcs
You have to add a custom controller resolver, which will bypass that limitation, and allow you to discover controller types from the executing assembly manually.
This was actually fixed already (now Web API controllers only need to be Visible not public), but that happened in September and the latest stable version of self host is from August.
So, add this:
public class ControllerResolver: DefaultHttpControllerTypeResolver {
public override ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver) {
var types = Assembly.GetExecutingAssembly().GetExportedTypes();
return types.Where(x => typeof(System.Web.Http.Controllers.IHttpController).IsAssignableFrom(x)).ToList();
}
}
And then register against your configuration, and you're done:
var conf = new HttpSelfHostConfiguration(new Uri(address));
conf.Services.Replace(typeof(IHttpControllerTypeResolver), new ControllerResolver());
Here is a full working example, I just tested against LinqPad. Note that you have to be running LinqPad as admin, otherwise you won't be able to listen at a port.
public class TestController: System.Web.Http.ApiController {
public string Get() {
return "Hello world!";
}
}
public class ControllerResolver: DefaultHttpControllerTypeResolver {
public override ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver) {
var types = Assembly.GetExecutingAssembly().GetExportedTypes();
return types.Where(x => typeof(System.Web.Http.Controllers.IHttpController).IsAssignableFrom(x)).ToList();
}
}
async Task Main() {
var address = "http://localhost:8080";
var conf = new HttpSelfHostConfiguration(new Uri(address));
conf.Services.Replace(typeof(IHttpControllerTypeResolver), new ControllerResolver());
conf.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var server = new HttpSelfHostServer(conf);
await server.OpenAsync();
// keep the query in the 'Running' state
Util.KeepRunning();
Util.Cleanup += async delegate {
// shut down the server when the query's execution is canceled
// (for example, the Cancel button is clicked)
await server.CloseAsync();
};
}
I need some help with this error "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection."
It's a asp.net mvc3, EF4, and ms sql.
Here is the razor with two dropdowns:
<div class="editRow">
#Html.DropDownListFor(m=>m.IndustryId, (SelectList)ViewBag.Industry, #Empower.Resource.General.ddlDefaultVal, new { #class = "ddl400" })
#Html.ValidationMessageFor(m => m.IndustryId)
</div>
<div class="editRow">
#Html.DropDownListFor(m=>m.ProvinceId, (SelectList)ViewBag.Province, #Empower.Resource.General.ddlDefaultVal, new {#class = "ddl400"})
#Html.ValidationMessageFor(m => m.ProvinceId)
</div>
Controller:
IndustryService indService = new IndustryService();
ViewBag.Industry = new SelectList(indService.GetAllIndustry(), "IndustryId", "IndustryName");
ProvinceService proService = new ProvinceService();
ViewBag.Province = new SelectList(proService.GetAllProvince(), "ProvinceId", "ProvinceName");
return View();
ProvinceService:
public IEnumerable<Province> GetAllProvince()
{
using (var context = DBContext.ObjectContext)
{
var pros = context.Provinces;
return pros;
}
}
IndustryService is identical as above...
public class DBContext
{
private static EmpowerDBEntities _empowerContext;
public static EmpowerDBEntities ObjectContext
{
get
{
if (_empowerContext == null)
_empowerContext = new EmpowerDBEntities();
return _empowerContext;
}
}
}
I know the problem occurs in second dropdown when it tries to retrive data while the connection is desposed by previous query. Please help me with this, thanks.
The fix is simple - convert to a .ToList() or First() before using. LINQ has deferred execution and tries to run this command after the context is disposed (when your object results are referenced) - not when you actually make the call.. You need to force it to run now while the context is in scope.
using (var context = DBContext.ObjectContext)
{
var pros = context.Provinces;
return pros.ToList();
}
Also - your code above is checking for null in the get accessor. However this object won't be null - it will be disposed, so you cannot do your check this way, you need to check if its null and not disposed.
public class DBContext
{
private static EmpowerDBEntities _empowerContext;
public static EmpowerDBEntities ObjectContext
{
get
{
if (_empowerContext == null || _empowerContext.IsDisposed())
_empowerContext = new EmpowerDBEntities();
return _empowerContext;
}
}
}
something like that anyways :)
I ran into a similar problem. I had been following this pattern, which I had seen in many code examples on the web:
public ActionResult Show(int id)
{
using (var db = new MyDbContext())
{
var thing = db.Things.Find(id);
return View(thing);
}
}
However, this was causing the ObjectDisposedException listed above whenever I tried to access anything that hadn't been loaded into memory in the View code (in particular, one-to-many relationships in the main view model).
I found a different pattern in this example:
public class MyController : Controller
{
private MyDbContext _db;
public MyController()
{
_db = new MyDbContext();
}
public ActionResult Show(int id)
{
// Do work such as...
var thing = _db.Things.Find(id);
return View(thing);
}
protected override void Dispose(bool disposing)
{
_db.Dispose();
base.Dispose(disposing);
}
}
This pattern keeps the database connection alive until the View has finished rendering, and neatly disposes of it when the Controller itself is disposed.
Here is the problem when you are using
using(var context=new CustomerContext())
{
return View(context.Customers.ToList());
}
when the block of code executes all references are disposed that you are lazzy loading so that's why it is throwing this error.
so i used
return View(context.Customers.ToList()) directly it will work perfectly fine.