I´m migratting a xamarin forms 3.x app with Prism to forms 4 with shell navigation.
Do I have to create my custom solution to pass complex parameters to the new page or Xamarin has some buildin feature to receive other than string parameters?
Thanks.
As far as I know, and, reading the docs, the only samples regard passing simple data, like string when navigating.
However, I was able to find an Issue (and Pull Request), for passing objects/ Models, for the next version (I assume this is the case you are referring to).
You can track it here.
I've run some tests which seems to be working. I'm relatively new to Xamarin, hence recommend caution and welcome any feedback for any potential issues I may be overlooking.
I wrote an extension to Shell to accept a data object parameter 'navigationData' as follows:-
await Shell.Current.GoToAsync(state, navigationData, animate);
The extension ...
namespace Xamarin.Forms
{
public static class ShellExtensions
{
public static async Task GoToAsync(this Shell shell, ShellNavigationState state, object navigationData, bool animate=false)
{
shell.Navigated += async (sender, e) =>
{
if ((Shell.Current?.CurrentItem?.CurrentItem as IShellSectionController)?.PresentedPage is MyContentPage
p) await p.InitializeAsync(navigationData).ConfigureAwait(false);
};
await shell.GoToAsync(state, animate);
}
}
}
As shown above the extension:-
hooks to the Shell 'Navigated' event,
retrieves the 'current view (page)' as 'MyContentPage' i.e. subclassed ContentPage,
calls an InitializeAsync method on the view passing in the
navigationData parameter
the view then calls an InitializeAsync
method on the binding context (view model) passing the
navigationData parameter onto the viewModel.
In the extension method above, 'MyContentPage' is a custom abstract subclass of ContentPage with an InitializeAsync(navigationData) method that simply calls a similar method on the viewModel (binding context of the view).
Similarily, ViewModels subclass a custom ViewModelBase class that has a virtual InitializeAsync(navigationData). This can be overridden in the viewModel with the desired implementation and handling of the navigation data.
Simplified sample of Views, ViewModels and related base classes shown below
using System.Threading.Tasks;
using MyXamarinApp.ViewModels;
using Xamarin.Forms;
namespace MyXamarinApp.Views
{
public ItemDetailPage : MyContent<ItemDetailViewModel>{}
public ItemPage : MyContentPage<ItemViewModel>{}
public abstract class MyContentPage<T> : MyContentPage where T : ViewModelBase
{
protected T Vm;
protected override ViewModelBase VmBase => Vm as ViewModelBase;
protected MyContentPage()
{
BindingContext = Vm = ViewModelLocator.Resolve<T>();
}
private Comand _showDetailCommand;
public Command ShowDetailCommand
{
get { return _showDetailCommand ??= new Command(async () =>
await Shell.Current.GoToAsync("itemDetail", new NavigationDataObject())); }
}
}
public abstract class MyContentPage : ContentPage
{
protected abstract ViewModelBase VmBase { get; }
public virtual async Task InitializeAsync(object navigationData)
{
await VmBase.InitializeAsync(navigationData);
}
}
}
public class NavigationDataObject
{
'Properties' etc.
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace MyXamarinApp.ViewModels
{
public ItemViewModel : ViewModelBase{}
public ItemDetailViewModel : ViewModelBase
{
private NavigationDataObject _navData;
public override async Task InitializeAsync(object navigationData)
{
if (navigationData is NavigationDataObject navData)
{
_navData = navData;
}
await base.InitializeAsync(navigationData);
}
}
public abstract class ViewModelBase
{
public virtual Task InitializeAsync(object navigationData)
{
return Task.FromResult(false);
}
}
}
You can always serialize the model to a JSON string and un-serializes it on the other side?
async void Handle_ItemTapped(object sender, ItemTappedEventArgs e)
{
if (e.Item == null)
return;
DailyPnL PnLClicked = (DailyPnL)e.Item;
string jason = await Task.Run(() => JsonConvert.SerializeObject(PnLClicked));
await Shell.Current.GoToAsync($"viewdailypnl?pnlmodel={jason}");
//Deselect Item
((ListView)sender).SelectedItem = null;
}
Then in your code behind:
public string pnlmodel
{
set
{
string derulo = Uri.UnescapeDataString(value);
viewModel.PnL = Task.Run(() => JsonConvert.DeserializeObject<DailyPnL>(derulo)).Result;
}
}
There is a framework called Xamarin.Zero https://github.com/markjackmilian/Xam.Zero
It lets you use shell while giving you convenient ViewModel to ViewModel navigation, IOC.
You can user stored preferences to store complex data like:
private async void OnItemSelected(Item item)
{
if (item == null)
return;
var jsonstr = JsonConvert.SerializeObject(item);
//Clear the shared preferences in case there is any
Preferences.Clear();
//Store your complex json on a shared preference
Preferences.Set("Data", jsonstr);
await Shell.Current.GoToAsync(nameof(DetailsPage));
}
Retrieve it on the details page like:
bool hasKey = Preferences.ContainsKey("Data");
var content = Preferences.Get("Data", string.Empty);
Details details = hasKey ? JsonConvert.DeserializeObject<Model>(content) : null;
Related
I am making a network call inside code behind constructor. I have to do this because I am passing parameter to constructor, then using those initiate the network call. Because of I am doing this inside the constructor I have to use Task.Run option which is not a good solution. I tried factory method also but I am unable to use it since passing parameters are done by click event. Can anyone suggest me a better solution. Thanks
Class Home{
private async void HomeLandingListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
var item = (HomeLanding)e.Item;
await Navigation.PushAsync(new PolicyDetailsMotor(item.id, p_end_date));
}
}
Network call class
public partial class PolicyDetailsMotor : ContentPage
{
public PolicyDetailsMotor(string id, string p_end_date)
{
InitializeComponent();
this.id = id;
this.p_end_date = p_end_date;
Title = "Motor Policy Details";
Task.Run(async () =>
{
var result = await api_class.getMotorPolicyDetails(id, p_end_date);
});
}
}
I have a following problem. I register my components and initialize them in Unity like this (example is for a Console application):
public class SharePointBootstrapper : UnityBootstrapper
{
...
public object Initialize(Type type, object parameter) =>
Container.Resolve(type,
new DependencyOverride<IClientContext>(Container.Resolve<IClientContext>(parameter.ToString())),
new DependencyOverride<ITenantRepository>(Container.Resolve<ITenantRepository>(parameter.ToString())));
public void RegisterComponents()
{
Container
.RegisterType<IClientContext, SharePointOnlineClientContext>(SharePointClientContext.Online.ToString())
.RegisterType<IClientContext, SharePointOnPremiseClientContext>(SharePointClientContext.OnPremise.ToString())
.RegisterType<ITenantRepository, DocumentDbTenantRepository>(SharePointClientContext.Online.ToString())
.RegisterType<ITenantRepository, JsonTenantRepository>(SharePointClientContext.OnPremise.ToString());
}
}
public enum SharePointClientContext
{
Online,
OnPremise
}
class Program
{
static void Main(string[] args)
{
...
bootstrap.RegisterComponents();
var bla = bootstrap.Initialize(typeof(ISharePointManager), SharePointClientContext.Online);
}
}
So, I register my components in MVC, WCF, Console etc. once with RegisterComponents() and initialize them with Initialize().
My question is, if I want to initialize specific named registration at runtime, from e.g. user input, can it be done otherwise as the code presented (with InjectionFactory or similar)?
This code works fine, but I'm not happy with its implementation. I have a feeling that it could be written in RegisterComponents() instead of Initialize() so that it accepts a parameter of some type, but I don't know how to do it.
Or, is maybe my whole concept wrong? If so, what would you suggest? I need to resolve named registration from a parameter that is only known at runtime, regardless of the technology (MVC, WCF, Console, ...).
Thanks!
Instead of doing different registrations, I would do different resolves.
Let's say that you need to inject IClientContext, but you want different implementations depending on a runtime parameter.
I wrote a similiar answer here. Instead of injecting IClientContext, you could inject IClientContextFactory, which would be responsible for returning the correct IClientContext. It's called Strategy Pattern.
public interface IClientContextFactory
{
string Context { get; } // Add context to the interface.
}
public class SharePointOnlineClientContext : IClientContextFactory
{
public string Context
{
get
{
return SharePointClientContext.Online.ToString();
}
}
}
// Factory for resolving IClientContext.
public class ClientContextFactory : IClientContextFactory
{
public IEnumerable<IClientContext> _clientContexts;
public Factory(IClientContext[] clientContexts)
{
_clientContexts = clientContexts;
}
public IClientContext GetClientContext(string parameter)
{
IClientContext clientContext = _clientContexts.FirstOrDefault(x => x.Context == parameter);
return clientContext;
}
}
Register them all, just as you did. But instead of injecting IClientContext you inject IClientContextFactor.
There also another solution where you use a Func-factory. Look at option 3, in this answer. One may argue that this is a wrapper for the service locator-pattern, but I'll leave that discussion for another time.
public class ClientContextFactory : IClientContextFactory
{
private readonly Func<string, IClientContext> _createFunc;
public Factory(Func<string, IClientContext> createFunc)
{
_createFunc = createFunc;
}
public IClientContext CreateClientContext(string writesTo)
{
return _createFunc(writesTo);
}
}
And use named registrations:
container.RegisterType<IClientContext, SharePointOnlineClientContext>(SharePointClientContext.Online.ToString());
container.RegisterType<IClientContext, SharePointOnPremiseClientContext>(SharePointClientContext.OnPremise.ToString());
container.RegisterType<IFactory, Factory>(
new ContainerControlledLifetimeManager(), // Or any other lifetimemanager.
new InjectionConstructor(
new Func<string, IClientContext>(
context => container.Resolve<IClientContext>(context));
Usage:
public class MyService
{
public MyService(IClientContextFactory clientContextFactory)
{
_clientContextFactory = clientContextFactory;
}
public void DoStuff();
{
var myContext = SharePointClientContext.Online.ToString();
IClientContextclientContext = _clientContextFactory.CreateClientContext(myContext);
}
}
Using AutoFixture with the AutoFixture.AutoMoq package, I sometimes find tests that weren't configured to correctly test the thing they meant to test, but the problem was never discovered because of the default (Loose) Mock behavior:
public interface IService
{
bool IsSomethingTrue(int id);
}
void Main()
{
var fixture = new Fixture()
.Customize(new AutoMoqCustomization());
var service = fixture.Freeze<Mock<IService>>();
Console.WriteLine(service.Object.IsSomethingTrue(1)); // false
}
I'd like to make Mocks get created with Strict behavior, so we're forced to call Setup() for the methods we expect to be called. I can do this for each individual mock like this:
fixture.Customize<Mock<IService>>(c => c.FromFactory(() => new Mock<IService>(MockBehavior.Strict)));
But after combing through source code for AutoMoqCustomization() and the various ISpecimenBuilder and other implementations, I'm pretty lost as to the best way to just make all Mocks get initialized with strict behavior. The framework appears to be very flexible and extensible, so I'm sure there's a simple way to do this--I just can't figure out how.
There's no simple built-in feature that will enable you to do something like that, but it shouldn't be that hard to do.
Essentially, you'd need to change MockConstructorQuery so that it invokes the constructor that takes a MockBehavior value, and pass in MockBehavior.Strict.
Now, you can't change that behaviour in MockConstructorQuery, but that class is only some 9-10 lines of code, so you should be able to create a new class that implements IMethodQuery by using MockConstructorQuery as a starting point.
Likewise, you'll also need to create a custom ICustomization that does almost exactly the same as AutoMoqCustomization, with the only exception that it uses your custom IMethodQuery with strict mock configuration instead of MockConstructorQuery. That's another 7 lines of code you'll need to write.
All that said, in my experience, using strict mocks is a bad idea. It'll make your tests brittle, and you'll waste a lot of time mending 'broken' tests. I can only recommend that you don't do this, but now I've warned you; it's your foot.
For those interested, down below you can find #MarkSeemann's reply translated into code. I am pretty sure it does not cover all use cases and it was not heavily tested. But it should be a good starting point.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Moq;
using Ploeh.AutoFixture;
using Ploeh.AutoFixture.AutoMoq;
using Ploeh.AutoFixture.Kernel;
namespace ConsoleApplication1
{
public class StrictAutoMoqCustomization : ICustomization
{
public StrictAutoMoqCustomization() : this(new MockRelay()) { }
public StrictAutoMoqCustomization(ISpecimenBuilder relay)
{
// TODO Null check params
Relay = relay;
}
public ISpecimenBuilder Relay { get; }
public void Customize(IFixture fixture)
{
// TODO Null check params
fixture.Customizations.Add(new MockPostprocessor(new MethodInvoker(new StrictMockConstructorQuery())));
fixture.ResidueCollectors.Add(Relay);
}
}
public class StrictMockConstructorMethod : IMethod
{
private readonly ConstructorInfo ctor;
private readonly ParameterInfo[] paramInfos;
public StrictMockConstructorMethod(ConstructorInfo ctor, ParameterInfo[] paramInfos)
{
// TODO Null check params
this.ctor = ctor;
this.paramInfos = paramInfos;
}
public IEnumerable<ParameterInfo> Parameters => paramInfos;
public object Invoke(IEnumerable<object> parameters) => ctor.Invoke(parameters?.ToArray() ?? new object[] { });
}
public class StrictMockConstructorQuery : IMethodQuery
{
public IEnumerable<IMethod> SelectMethods(Type type)
{
if (!IsMock(type))
{
return Enumerable.Empty<IMethod>();
}
if (!GetMockedType(type).IsInterface && !IsDelegate(type))
{
return Enumerable.Empty<IMethod>();
}
var ctor = type.GetConstructor(new[] { typeof(MockBehavior) });
return new IMethod[]
{
new StrictMockConstructorMethod(ctor, ctor.GetParameters())
};
}
private static bool IsMock(Type type)
{
return type != null && type.IsGenericType && typeof(Mock<>).IsAssignableFrom(type.GetGenericTypeDefinition()) && !GetMockedType(type).IsGenericParameter;
}
private static Type GetMockedType(Type type)
{
return type.GetGenericArguments().Single();
}
internal static bool IsDelegate(Type type)
{
return typeof(MulticastDelegate).IsAssignableFrom(type.BaseType);
}
}
}
Usage
var fixture = new Fixture().Customize(new StrictAutoMoqCustomization());
My app flow is as follows (simplified for clarity):
User GETs a page from "/page1"
User performs actions on the page (adds text, clicks, etc..), while Signalr communicates this data to the server, which performs heavy calculations in the background, and the results of those are returned to the page (lets call those "X").
When the user is finished with the page, he clicks a link to "/page2", that is returned by Nancy. This page is built using a Model that is dependent on X.
So, how do I build that Model based on X? How can signalr write to the user session in a way that Nancy can pick up on?
(I'm looking for a "clean" way)
Pending formal integration of Signalr & Nancy, this is what I came with. Basically, I share an IOC container between the two, and use an object (singleton lifetime) that maps users to state.
How to share an IOC container using the built in TinyIOC:
Extend Signalr's DefaultDependencyResolver
public class TinyIoCDependencyResolver : DefaultDependencyResolver
{
private readonly TinyIoCContainer m_Container;
public TinyIoCDependencyResolver(TinyIoCContainer container)
{
m_Container = container;
}
public override object GetService(Type serviceType)
{
return m_Container.CanResolve(serviceType) ? m_Container.Resolve(serviceType) : base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
var objects = m_Container.CanResolve(serviceType) ? m_Container.ResolveAll(serviceType) : new object[] { };
return objects.Concat(base.GetServices(serviceType));
}
}
Replace Signalr's default DependencyResolver with our new one
public class Bootstrapper : DefaultNancyBootstrapper
{
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
CookieBasedSessions.Enable(pipelines);
// Replace UserToStateMap with your class of choice
container.Register<IUserToStateMap, UserToStateMap>();
GlobalHost.DependencyResolver = new TinyIoCDependencyResolver(container);
RouteTable.Routes.MapHubs();
}
}
Add IUserToStateMap as a dependency in your hubs and Nancy modules
public class MyModule : NancyModule
{
public MyModule(IUserToStateMap userToStateMap)
{
Get["/"] = o =>
{
var userId = Session["userId"];
var state = userToStateMap[userId];
return state.Foo;
};
}
}
public class MyHub : Hub
{
private readonly IUserToStateMap m_UserToStateMap;
public MyHub(IUserToStateMap userToStateMap)
{
m_UserToStateMap = userToStateMap;
}
public string MySignalrMethod(string userId)
{
var state = userToStateMap[userId];
return state.Bar;
}
}
What I would really want, is a way to easily share state between the two based on the connection ID or something like that, but in the meantime this solution works for me.
Did you arrive hear looking for a simple example of how to integrate Nancy and SignalR? I know I did.
Try this question instead (I self-answered it).
SignalR plus NancyFX : A simple but well worked example
I've been reading a lot on TDD over the past few months and decided to jump in and try it out with an easy example, I'm just not sure I'm testing for the right things in practice. Here the tests for a custom Data Annotation for validating emails:
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MembershipTest.Tests
{
[TestClass]
public class CustomDataAnnotationsTest
{
[TestMethod]
public void CustomDataAnnotations_Email_ReturnTrueIfNull()
{
// Arrange
EmailAttribute attribute = new EmailAttribute();
// Act
bool result = attribute.IsValid(null);
// Assert
Assert.AreEqual(true, result);
}
[TestMethod]
public void CustomDataAnnotations_Email_ReturnFalseIfInvalid()
{
// Arrange
EmailAttribute attribute = new EmailAttribute();
// Act
bool result = attribute.IsValid("()[]\\;:,<>#example.com");
// Assert
Assert.AreEqual(false, result);
}
[TestMethod]
public void CustomDataAnnotations_Email_ReturnTrueIfValid()
{
// Arrange
EmailAttribute attribute = new EmailAttribute();
// Act
bool result = attribute.IsValid("john.smith#example.com");
// Assert
Assert.AreEqual(true, result);
}
}
}
And here is the subsequent code written for the test:
using System;
using System.ComponentModel.DataAnnotations;
using System.Net.Mail;
public class EmailAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
//Let RequiredAttribute validate whether the value is required or not.
if (value == null)
{
return true;
}
//Check to see if System.Net.Mail can send to the address.
try
{
var i = new MailAddress(value.ToString());
}
catch (Exception)
{
return false;
}
return true;
}
}
All tests failed initially and then succeeded after writing the code, but are the tests appropriately written? Too much, or too little? I know this is a very simple example, but I want to make sure I'm on the right track before moving on to more complicated things.
I think you are on the right track. At this point I would suggest some refactoring in your tests. Since you are using
EmailAttribute attribute = new EmailAttribute();
in every test. I would suggest creating TestInitialize() and TestCleanup() methods. The TestInitialize would new EmailAttribute and the TestCleanup would null the object out. This is just a matter of preference. Like this
private EmailAttribute _attribute;
[TestInitialize]
public void TestInitialize()
{
_attribute = new EmailAttribute
}
[TestCleanup]
public void TestCleanup()
{
_attribute = null;
}