I'm currently building a Xamarin.Forms project using MVVMCross. In order to test my platform specific code I am using Nunit.Xamarin which features an app that run tests on device.
This test app is a forms app but doesn't use MVVMCross and I haven't had any luck setting it up to use MVVMCross due to the fact the Application class loads an App of type NUnit.Runner.App whereas MVVMCross requires MvxFormsApp.
I want to test this class the saves and loads user data from an SQLite Database:
public class DataStorageService : IDataStorageService
{
private readonly SQLiteConnection _connection;
public User UserData
{
get { return _connection.Table<User>().FirstOrDefault(); }
set { _connection.InsertOrReplace(value); }
}
public DataStorageService(IMvxSqliteConnectionFactory factory)
{
_connection = factory.GetConnection(DataStorageConstants.LocalDatabaseName);
_connection.CreateTable<User>();
}
}
I want to actually test that it saves and loads from a local SQLite database so I don't want to mock the IMvxSqliteConnectionFactory. I tried installing MVVMCross and the SQLite plugin into the project and then passing in the Android implementation of the connection factory but that repeatedly threw a typeloadexception.
Any ideas as to how I can set up this test with MVVMCross (or are there alternatives?) and dependency injection?
It is possible :) The important stuff happens in the MvxSplashScreenActivity. The MvxFormsApp is basically empty. So we don't have to care. Example Code: https://github.com/smstuebe/stackoverflow-answers/tree/master/mvx-android-test-app
Create a nunit Test app project
Install-Package MvvmCross.StarterPack -Version 4.1.4
Get rid of Views folder
Install the SQLite plugin
Reference your Core project
Install-Package MvvmCross.Forms.Presenter -Version 4.1.4
Remove MainLauncher = true from MainActivity
Adust Setup to return your core project's App
protected override IMvxApplication CreateApp()
{
return new MyApp.Core.App();
}
Change SplashScreen to (source)
[Activity(MainLauncher = true
, Theme = "#style/Theme.Splash"
, NoHistory = true
, ScreenOrientation = ScreenOrientation.Portrait)]
public class SplashScreen
: MvxSplashScreenActivity
{
public SplashScreen()
: base(Resource.Layout.SplashScreen)
{
}
private bool _isInitializationComplete;
public override void InitializationComplete()
{
if (!_isInitializationComplete)
{
_isInitializationComplete = true;
StartActivity(typeof(MainActivity));
}
}
protected override void OnCreate(Android.OS.Bundle bundle)
{
Forms.Init(this, bundle);
Forms.ViewInitialized += (object sender, ViewInitializedEventArgs e) =>
{
if (!string.IsNullOrWhiteSpace(e.View.StyleId))
{
e.NativeView.ContentDescription = e.View.StyleId;
}
};
base.OnCreate(bundle);
}
}
Write a test like
[TestFixture]
public class TestClass
{
[Test]
public void TestMethod()
{
var service = Mvx.Resolve<IDataStorageService>();
Assert.IsNull(service.UserData);
}
}
Enjoy the awesomeness of MvvmCross
Related
what is the proper way to create an EF Core database on an Android/iOS device with Xamarin.Forms and EntityFrameworkCore?
I've come across the EnsureCreated vs Migrate thing and since I'm planning on changing the database structure in the future, I'd like to be able to apply those migrations to an existing database, thus I've chosen the Migrate approach.
However, when I call the Migrate method, it doesn't seem to create a database. I've managed to make it work in case I copy the pre-generated database file on the devices beforehand, but that's not what I want. Is there a way to tell EF Core to check if there's an existing database, create a new database if not, and then apply pending migrations to it?
Here's what I've done so far:
I've created a .net standard class library "Data" that will hold all my database classes (context, models, and migrations).
I've changed the TargetFramework of the "Data" project from <TargetFramework>netstandard2.0</TargetFramework> to <TargetFrameworks>netcoreapp2.0;netstandard2.0</TargetFrameworks> so I could run PMC commands on it.
I've installed these nuget packages to the "Data" project:
EntityFrameworkCore
EntityFrameworkCore.Sqlite
EntityFrameworkCore.Tools
EntityFrameworkCore.Design
I've created a database context class. I've made two constructors for it. The default one will only be used by the PMC commands for generating migrations. The other one will be used in production.
public class MyTestContext : DbContext
{
private string _databasePath;
public DbSet<Issue> Issues { get; set; }
[Obsolete("Don't use this for production. This is only for creating migrations.")]
public MyTestContext() : this("nothing.db")
{
}
public MyTestContext(string databasePath)
{
_databasePath = databasePath;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//I've also tried Filename={_databasePath} here, didn't work either
optionsBuilder.UseSqlite($"Data Source={_databasePath}");
}
}
I've created a test model class, just to have something to play with
public class Issue
{
public string Id { get; set; }
}
I created an inital migration using this command, which completed successfully:
Add-Migration Migration001 -Context MyTestContext -Output Migrations
I added a call to the Migrate method in the App.xaml.cs of my Xamarin.Forms project to test if it works.
public partial class App : Application{
//... other code
protected override async void OnStart()
{
base.OnStart();
using (var db = new MyTestContext(_dbPath))
{
try
{
db.Database.Migrate();
db.Issues.Add(new Issue
{
Id = "TestIssueId"
});
db.SaveChanges();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
}
//... other code
}
The _dbPath variable contains this (on Android where I'm testing it):
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "test.db");
After all this, I'm getting this exception:
Microsoft.Data.Sqlite.SqliteException: SQLite Error 1: 'no such table: Issues'.
I've got a Prism application with DryIoc as container.
I'd like IHttpClientFactory to provide HttpClients to my typed clients, which are like this:
public class ExampleService : IExampleService
{
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetExamplesAsync()
{
// Code deleted for brevity.
}
}
In App.xaml.cs I register my typed client so they can be injected in viewmodels with the following:
public partial class App
// ...
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// Code deleted for brevity.
containerRegistry.Register<IExampleService, ExampleService>();
}
And that's before trying to use IHttpClientFactory.
Now, to add it, we should AddHttpClient() on IServiceCollection. That's where I thought DryIoc.Microsoft.DependencyInjection was needed, so, still in App.xaml.cs, I wrote the following:
public partial class App
// ...
protected override IContainerExtension CreateContainerExtension()
{
var services = new ServiceCollection();
services.AddHttpClient<IExampleService, ExampleService>(c =>
{
c.BaseAddress = new Uri("https://api.example.com/");
});
var container = new Container(CreateContainerRules())
.WithDependencyInjectionAdapter(services);
return new DryIocContainerExtension(container);
}
The problem is that in my ExampleService I'm getting client with the following specs:
{
"DefaultRequestHeaders":[
],
"BaseAddress":null,
"Timeout":"00:01:40",
"MaxResponseContentBufferSize":2147483647
}
whilst I expected BaseAddress to be https://api.example.com/, so the REST API call fails.
What is the correct pattern to use IServiceProvider when using Prism for Xamarin.Forms with DryIoc? Unfortunately there's no documentation or open source code available on the following matter, and I am kind of lost.
Thanks you, and have a great day.
UPDATE #1
As per kind Dan S. guidance, DryIoc.Microsoft.DependencyInjection was uninstalled so the project came back at its state before trying to use IServiceCollection dependencies (in my case, IHttpClientFactory), then I installed Prism.Forms.Extended and later Prism.DryIoc.Extensions.
After that CreateContainerExtension() in App.xaml.cs became:
protected override IContainerExtension CreateContainerExtension()
{
var containerExtension = PrismContainerExtension.Current;
containerExtension.RegisterServices(s =>
{
s.AddHttpClient<IExampleService, ExampleService>(c =>
{
c.BaseAddress = new Uri("https://api.example.com/");
});
});
return containerExtension;
}
and containerRegistry.Register<IExampleService, ExampleService>(); was removed from RegisterTypes().
Now ExampleService finally gets its HttpClient injected and everything is working.
UPDATE #2
The only packages related to Prism I am using are Prism.DryIoc.Forms and Prism.DryIoc.Extensions.
I completely removed the override of CreateContainerExtension() in App.xaml.cs and refactored RegisterTypes() to
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// Code deleted for brevity.
containerRegistry.RegisterServices(s =>
{
s.AddHttpClient<IExampleService, ExampleService>(c =>
{
c.BaseAddress = new Uri("https://api.example.com/");
});
});
}
This way I get thrown a NotImplementedException.
However, by overriding CreateContainerExtension() with the following:
protected override IContainerExtension CreateContainerExtension() => PrismContainerExtension.Current;
Everything is finally back to working!
If you want to use IServiceCollection extensions such as AddHttpClient I would suggest that you use the Prism Container Extensions. In your case it would be Prism.DryIoc.Extensions. The Container Extensions provide a lot of additional support including support for registering services with via the Service Collection extensions.
You can either install Prism.Forms.Extended and it will all just work, or you can update your App as follows:
protected override IContainerExtension CreateContainerExtension() =>
PrismContainerExtension.Current;
Adding as this is the only post I've found in weeks of searching that explains how to do this.
I'm using Unity rather than Dryloc but the solution is the same.
Install ONLY these additional packages:
Prism.Forms.Extended
Prism.Unity.Extensions
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//Omitted Code
containerRegistry.RegisterServices(serviceCollection =>
{
serviceCollection.AddHttpClient<IApiService, ApiService>(client =>
{
client.BaseAddress = new Uri("Your Address Here");
});
});
}
public ApiService(HttpClient client)
{
//Do Stuff
}
The projects is planned to target multiplatform, so I pull the maximum of code in class libraries so that it can be easily reused.
The architecture is based on the Model-View-Presenter principle.
The project structure is the following:
Solution
-> Domain
-> Domain.Tests
-> MVP
-> MVP.Tests
-> Web
-> Web.Tests
-> Windows
-> Windows.Tests
MVP
This project contains the presenters and the views of the project. For example:
public interface IApplicationView : IView, IHasUiHandler<IApplicationUiHandler> {
}
public class ApplicationPresenter : Presenter<IApplicationView>
, IApplicationUiHAndler {
public ApplicationPresenter(IApplicationView view) : base(view) {
View.Handler = this;
}
}
Windows
This project contains the WPF GUI of the application, and the so-called Composition Root. For example:
public class ApplicationWindow : Window, IApplicationView {
}
public class App : Application {
protected override void OnStartUp(StartupEventArgs e) {
IKernel kernel = new StandardKernel();
kernel.Bind(x => x
.FromThisAssembly()
.SelectAllClasses().EndingWith("Window")
.BindAllInterfaces().EndingWith("View") // Here's what I'd like to do.
);
}
}
Web
This project contains the ASP.NET GUI pages for the application, and the so-called Composition Root.
public class ApplicationPage : Page, IApplicationView {
}
public class MvcApplication : HttpApplication {
protected override void Application_Start() {
IKernel kernel = new StandardKernel();
kernel.Bind(x => x
.FromThisAssembly()
.SelectAllClasses().EndingWith("Page")
.BindAllInterfaces().EndingWith("View") // Here's what I'd like to do.
}
}
Well, I guess you get the idea...
I'm quite new to Dependency Injection, and even newer in convention binding.
I'd like to know how to configure the bindings using the conventions, with Ninject.
Any idea how to bind those views with Windows (WPF) and Pages (Web)?
EDIT
After trying what #BatteryBackupUnit suggested, I guess my problem is all about my search of assemblies.
using (var kernel = new StandardKernel())
kernel.Bind(scanner => scanner
.From(AppDomain.CurrentDomain
.GetAssemblies()
.Where(a => (a.FullName.Contains("MyProject.MVP")
|| a.FullName.Contains("Windows"))
&& !a.FullName.Contains("Tests")))
.SelectAllClasses()
.EndingWith("Window")
.BindSelection((type, baseType) =>
type.GetInterfaces().Where(iface => iface.Name.EndsWith("View"))));
As stated previously, the View interfaces aren't located in the same assembly as the Window classes. The above code, base on #BatteryBackupUnit's answer, works great.
How about this:
using FluentAssertions;
using Ninject;
using Ninject.Extensions.Conventions;
using System.Linq;
using Xunit;
public interface ISomeView { }
public interface ISomeOtherView { }
public interface INotEndingWithViewWord { }
public class SomePage : ISomeView, ISomeOtherView, INotEndingWithViewWord
{
}
public class Demo
{
[Fact]
public void Test()
{
using (var kernel = new StandardKernel())
{
kernel.Bind(x => x
.FromThisAssembly()
.SelectAllClasses()
.EndingWith("Page")
.BindSelection((type, baseType) =>
type.GetInterfaces()
.Where(iface => iface.Name.EndsWith("View"))));
kernel.Get<ISomeView>().Should().BeOfType<SomePage>();
kernel.Get<ISomeOtherView>().Should().BeOfType<SomePage>();
kernel.Invoking(x => x.Get<INotEndingWithViewWord>())
.ShouldThrow<ActivationException>();
}
}
}
Note: I've been using nuget packages
Ninject
Ninject.Extensions.Conventions
xunit.net
FluentAssertions
of those xunit.net and FluentAssertions are only there to run the test and not be used in production.
Or you could also use .BindWith<T : IBindingGenerator> or .BindUsingRegex(...).
After updating to latest Asp.Net Identity (from 1.0 to 2.0) i got an exception on CRUD of a user from DB:
The entity type User is not part of the model for the current context
// for example here:
var manager = Container.Resolve<UserManager<User>>();
IdentityResult result = manager.Create(user, "Test passwd");
public class User : IdentityUser
{
// Some properties
}
public class AppDbContext : IdentityDbContext<User>
{
static AppDbContext()
{
// Without this line, EF6 will break.
Type type = typeof (SqlProviderServices);
}
public AppDbContext()
: this("DBName")
{
}
public AppDbContext(string connectionString)
: base(connectionString)
{
}
...
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<User>()
.HasMany(c => c.SkippedTopics)
.WithMany(i => i.Users)
.Map(t =>
t.MapLeftKey("User_Id")
.MapRightKey("Topic_Id")
.ToTable("UserSkippedTopics"));
...
}
Before the update the code worked correctly. Any ideas?
UPDATE
The problem was in Unity configuration for UserManager types.
Fix: i created empty AppUserStore: UserStore and registered it as IUserStore:
public class AppUserStore : UserStore<User>
{
public AppUserStore(AppDbContext context) : base(context)
{
}
}
...
public static void RegisterTypes(IUnityContainer container)
{
...
_container.RegisterType<AppDbContext, AppDbContext>(new InjectionConstructor());
_container.RegisterType<IUserStore<User>, AppUserStore>();
}
shouldn't there be UserStore somewhere? I mean looks like you're using Unity for DI which I've never used so I don't know how it works but normally to instantiate UserManager you do something like this:
var manager = new UserManager<User>(new UserStore<User>(new AppDbContext()));
The same thing happened to me and it sucks. You need to enable-immigration and update your database from the Nuget manager console.
Check out this blog. Link is here
This just happened to me also when upgrading from Identity 1.0 to 2.0. The identity code scaffold by Visual Studio (such as the AccountController, ApplicationOAuthProvider, Startup.Auth.cs, etc) was generated when I created the project in Asp.Net Identity 1.0. It seems that Visual Studio had an update and now generates slightly different code for Identity 2.0.
The solution for me was to generate a new web app and copy over all the (new) generated code into the project I just upgraded, replacing the old generated code.
I'm starting a web application that contains the following projects:
Booking.Web
Booking.Services
Booking.DataObjects
Booking.Data
I'm using the repository pattern in my data project only. All services will be the same, no matter what happens. However, if a customer wants to use Access, it will use a different data repository than if the customer wants to use SQL Server.
I have StructureMap, and want to be able to do the following:
Web project is unaffected. It's a web forms application that will only know about the services project and the dataobjects project.
When a service is called, it will use StructureMap (by looking up the bootstrapper.cs file) to see which data repository to use.
An example of a services class is the error logging class:
public class ErrorLog : IErrorLog
{
ILogging logger;
public ErrorLog()
{
}
public ErrorLog(ILogging logger)
{
this.logger = logger;
}
public void AddToLog(string errorMessage)
{
try
{
AddToDatabaseLog(errorMessage);
}
catch (Exception ex)
{
AddToFileLog(ex.Message);
}
finally
{
AddToFileLog(errorMessage);
}
}
private void AddToDatabaseLog(string errorMessage)
{
ErrorObject error =
new ErrorObject
{
ErrorDateTime = DateTime.Now,
ErrorMessage = errorMessage
};
logger.Insert(error);
}
private void AddToFileLog(string errorMessage)
{
// TODO: Take this value from the web.config instead of hard coding it
TextWriter writer = new StreamWriter(#"E:\Work\Booking\Booking\Booking.Web\Logs\ErrorLog.txt", true);
writer.WriteLine(DateTime.Now.ToString() + " ---------- " + errorMessage);
writer.Close();
}
}
I want to be able to call this service from my web project, without defining which repository to use for the data access. My boostrapper.cs file in the services project is defined as:
public class Bootstrapper
{
public static void ConfigureStructureMap()
{
ObjectFactory.Initialize(x =>
{
x.AddRegistry(new ServiceRegistry());
}
);
}
public class ServiceRegistry : Registry
{
protected override void configure()
{
ForRequestedType<IErrorLog>().TheDefaultIsConcreteType<Booking.Services.Logging.ErrorLog>();
ForRequestedType<ILogging>().TheDefaultIsConcreteType<SqlServerLoggingProvider>();
}
}
}
What else do I need to get this to work? When I defined a test, the ILogger object was null.
Perhaps some details on how you are calling this code from a test would be useful.
My understanding is that you need to ensure that the ConfigureStructureMap call has been made early in the applications life (e.g. in the Global.asax in a web project).
After that you would be calling for instances of IErrorLog using something like:
IErrorLog log = StructureMap.ObjectFactory.GetNamedInstance<IErrorLog>();