Xamarin Forms w/ Mobile Blazor Bindings - How to specify dependency injection in Blazor app for class defined in Android / iOS projects - xamarin.forms

Within my Blazor app, I am trying to determine the amount of available storage of the Android or iOS device.
I have found an answer here that is relevant:
Can I check available storage in the Device on Xamarin Forms?
As per the accepted answer, I have implemented the following:
Defined the Interface in the Blazor project
public interface IStorage
{
double GetRemainingStorage();
}
Created a class in the Android project
[assembly: Xamarin.Forms.Dependency(typeof(AndroidStorageManager))]
namespace MyApp.Android
{
public class AndroidStorageManager : IStorage
{
public double GetRemainingStorage()
{
var freeSpace = Android.OS.Environment.ExternalStorageDirectory.UsableSpace;
return freeSpace;
}
}
}
Created a class in the iOS project
[assembly: Xamarin.Forms.Dependency(typeof(iOSStorageManager))]
namespace MyApp.iOS
{
public class iOSStorageManager : IStorage
{
public double GetRemainingStorage()
{
var freeSpace = NSFileManager.DefaultManager.GetFileSystemAttributes(Environment.GetFolderPath(Environment.SpecialFolder.Personal)).FreeSize;
return freeSpace;
}
}
}
The Issue
Despite the definition of the dependencies with the [assembly: attribute, when my Blazor app tries to inject an instance of IStorage, it throws an exception:
Unable to resolve service for type 'IStorage' ...
Of course, I'm not able to define the dependency injection for IService in the Blazor app on startup, since the AndroidStorageManager and iOSStorageManager are defined in the device-specific projects which are already referencing the Blazor app.
I suspect I need some other way of defining the DI for IService in the Android and iOS apps, but can't see how to do it.
Can anyone offer any advice?

I seem to have found a workaround for my immediate issue, but I'm sure there must be a more elegant solution.
The exception was being generated when trying to inject an instance of the class associated with the IStorage interface.
public class MyManager {
private IStorage _storage;
public MyManager(IStorage storage) {
_storage = storage;
}
...
}
Rather then relying on the Blazor DI to resolve this dependency, I was able to use Xamarin.Forms DI to inject an instance manually, as follows:
public class MyManager {
private IStorage _storage;
public MyManager() {
_storage = DependencyService.Get<IStorage>();
}
...
}
I'm assuming that my original code wasn't working because Xamarin.Forms and Blazor are not sharing the same DI container. I'm not sure.
I'd be interested in anyone that can suggest a more elegant solution.

Related

.Net Maui Class library Dependancy injection for Platform specific code plugin

I am trying to create a plugin using .Net Maui class libraries that use native Android and iOS code. I tried using the Dependency Service using the Interface, but I get a null point when I try to Register the Interface.
Shared code
public class MyPlugin
{
public static DisplayModel()
{
IDeviceOrientationService service =
DependencyService.Get<IMyPlugin>();
var model = service.GetDeviceName();
}
}
public Interface IMyPlugin
{
string GetDeviceName();
}
Android platform
[assembly: Dependency(typeof(MyPlugin))]
public class MyPlugin:IMyPlugin
{
public string GetDeviceName
{
return "From Android";
}
}
This is just an example. Please help me understand if I am not going about this the correct way and any suggestion would be appreciated.

How to do Login with Firebase integration in .NET MAUI?

I'm trying to login with integration to social networks, more specifically to Google in .NET MAUI. I've done it with Xamarin Forms and it worked perfectly, however, in MAUI a standard error is occurring:
Error CS0246 The type or namespace name 'Android' could not be found (are you missing a using directive or an assembly reference?) LoginWithRedes (net6.0-ios), LoginWithRedes (net6.0-maccatalyst), LoginWithRedes (net6.0-windows10.0.19041) C:\MAUI\LoginWithRedes\LoginWithRedes\Platforms\Android\GoogleManager.cs
Libraries not being recognized
Packages I added to the project
Code of the GoogleManager.CS Class where the standard error occurs to me:
`[assembly: Dependency(typeof(GoogleManager))]
namespace LoginWithRedes.Platforms.Android
{
public class GoogleManager : Java.Lang.Object, IGoogleManager, GoogleApiClient.IConnectionCallbacks, GoogleApiClient.IOnConnectionFailedListener
{
public static GoogleApiClient _googleApiClient { get; set; }
public static GoogleManager Instance { get; private set; }
public bool IsLogedIn { get; set; }
Context _context;
public GoogleManager()
{
_context = global::Android.App.Application.Context;
Instance = this;
}
public void Login()
{
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DefaultSignIn)
.RequestEmail()
.Build();
_googleApiClient = new GoogleApiClient.Builder((_context).ApplicationContext)
.AddConnectionCallbacks(this)
.AddOnConnectionFailedListener(this)
.AddApi(Auth.GOOGLE_SIGN_IN_API, gso)
.AddScope(new Scope(Scopes.Profile))
.Build();
Intent signInIntent = Auth.GoogleSignInApi.GetSignInIntent(_googleApiClient);
((MainActivity)Forms.Context).StartActivityForResult(signInIntent, 1);
_googleApiClient.Connect();
}
public void Logout()
{
var gsoBuilder = new GoogleSignInOptions.Builder(GoogleSignInOptions.DefaultSignIn).RequestEmail();
GoogleSignIn.GetClient(_context, gsoBuilder.Build())?.SignOut();
_googleApiClient.Disconnect();
}
public void OnAuthCompleted(GoogleSignInResult result)
{
if (result.IsSuccess)
{
IsLogedIn = true;
Application.Current.MainPage = new MainPage();
}
else
{
}
}`
OnActivityResult method that I implemented in MainActivity class
If anyone can help me with this error, I would be very grateful.
Note: I'm new to Xamarin and Maui.
Thank you very much in advance
I'm also new to Maui, and in my experience, these errors were caused by using native Xamarin libraries in Maui. Xamarin targets each platform separately using separate nuget packages. Maui's combined 1-project architecture means you need to use packages that work for both at once.
At least when I was getting started a few months ago, these weren't readily available. Firebase client was not yet released for .NET 6.0 (Multiplatform by default).
Things may have changed since then. But I had great success using Firebase with this plugin https://github.com/TobiasBuchholz/Plugin.Firebase. It wraps up the platform specific libraries into a single project API, with a really easy to use c# interface. You can use await and stuff as you would expect. Calling the native APIs was difficult, and required a lot of code duplication. This plugin saves a lot of time, and I haven't yet run into any problems.
The documentation on the plugin is a bit sparse, but hey, it's free and works.

Consume OpenApi client .NET Core with Interface

Someone out there must have run into this already...
I created a WebApi solution with swagger implemented, full documentation, the whole 9 yards!
When I run my web api solution, see the swagger output (and I've tested the endpoints, all working fine)
I can see the swagger definition: https://localhost:5001/swagger/v1/swagger.json
Now, I want to consume this Api as a connected service on my web app.
So following every single tutorial online:
I go to my webapp
right click on Connected Services
Add Connected Service
Add Service Reference > OpenApi > add Url, namespace & class name
That generates a partial class in my solution (MyTestApiClient)
public parial class MyTestApiClient
{
// auto generated code
}
Next step, inject the service in Startup.cs
services.AddTransient(x =>
{
var client = new MyTestApiClient("https://localhost:5001", new HttpClient());
return client;
});
Then, inject the class into some class where it's consumed and this all works
public class TestService
{
private readonly MyTestApiClient _client; // this is class, not an interface -> my problem
public TestService(MyTestApiClient client)
{
_client = client;
}
public async Task<int> GetCountAsync()
{
return _client.GetCountAsync();
}
}
So everything up to here works. BUT, this generated OpenApi client doesn't have an interface which sucks for the purposes of DI and Unit Testing.
I got around this by creating a local interface IMyTestApiClient, added to the generated class (MyTestApiClient). I only have 1 endpoint in my WebApi so have to declare that on my interface.
public parial class MyTestApiClient : IMyTestApiClient
{
// auto generated code
}
public interface IMyTestApiClient
{
// implemented in generated MyTestApiClient class
Task<int> GetCountAsync();
}
services.AddTransient<IMyTestApiClient, MyTestApiClient>(x =>
{
IMyTestApiClient client = new MyTestApiClient("https://localhost:5001", new HttpClient());
return client;
});
public class TestService
{
private readonly IMyTestApiClient _client; // now injecting local interface instead of the generated class - great success
public TestService(IMyTestApiClient client)
{
_client = client;
}
public async Task<int> GetCountAsync()
{
return _client.GetCountAsync();
}
}
But this is a bad approach because it makes me manually create an interface and explicitly declare the methods I want to consume. Furthermore, every time my Api gets updated, I will have to tweak my local interface.
So question time:
How can I add an OpenApi Service Reference that automagically also generates an interface as well?
Thanks in advance for any help getting to a viable solution.
You may have already found the answer but I had the same issue and managed to resolve it by adding /GenerateClientInterfaces:true in the Options section for the OpenAPI reference in my .csproj:
<OpenApiReference Include="api.json" CodeGenerator="NSwagCSharp" Namespace="MyNamespace" ClassName="MyClassName">
<SourceUri>https://localhost:7040/swagger/v1/swagger.json</SourceUri>
<OutputPath>MyClient.cs</OutputPath>
<Options>/GenerateClientInterfaces:true</Options>
</OpenApiReference>

DI in Azure Functions

I have some class libraries that I use in my ASP.NET Web API app that handle all my backend stuff e.g. CRUD operations to multiple databases like Azure SQL Database, Cosmos DB, etc.
I don't want to re-invent the wheel and able to use them in a new Azure Functions that I'm creating in Visual Studio 2017. All my repository methods use an interface. So, how will I implement dependency injection in my new Azure function?
I'm not seeing any support for DI but I'm a bit confused. It appears Azure Functions are based on the same SDK as WebJobs and I think last year Microsoft had started supporting DI in WebJobs - I know for sure because I implemented it using Ninject.
Is there way around this so that I can use my existing libraries in my new Azure Functions project?
I see these two techniques in addition to the service locator (anti)pattern. I asked the Azure Functions team for their comments as well.
https://blog.wille-zone.de/post/azure-functions-dependency-injection/
https://blog.wille-zone.de/post/azure-functions-proper-dependency-injection/
There is an open feature request on the GitHub pages for Azure Functions concerning this matter.
However, the way I'm approaching this is using some kind of 'wrapper' entry point, resolve this using the service locator and and start the function from there.
This looks a bit like this (simplified)
var builder = new ContainerBuilder();
//register my types
var container = builder.Build();
using(var scope = container.BeginLifetimeScope())
{
var functionLogic = scope.Resolve<IMyFunctionLogic>();
functionLogic.Execute();
}
This is a bit hacky of course, but it's the best there is until there is at the moment (to my knowledge).
I've seen the willie-zone blog mentioned a lot when it comes to this topic, but you don't need to go that route to use DI with Azure functions.
If you are using Version2 you can make your Azure functions non-static. Then you can add a public constructor for injecting your dependencies. The next step is to add an IWebJobsStartup class. In your startup class you will be able to register your services like you would for any other .Net Core project.
I have a public repo that is using this approach here: https://github.com/jedi91/MovieSearch/tree/master/MovieSearch
Here is a direct link to the startup class: https://github.com/jedi91/MovieSearch/blob/master/MovieSearch/Startup.cs
And here is the function: https://github.com/jedi91/MovieSearch/blob/master/MovieSearch/Functions/Search.cs
Hope this approach helps. If you are wanting to keep your Azure Functions static then the willie-zone approach should work, but I really like this approach and it doesn't require any third party libraries.
One thing to note is the Directory.Build.target file. This file will copy your extensions over in the host file so that DI will work once the function is deployed to Azure. Running the function locally does not require this file.
Azure Functions Depdendency Injection was announced at MSBuild 2019. Here's an example on how to do it:
[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]
namespace MyNamespace
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
builder.Services.AddSingleton((s) => {
return new CosmosClient(Environment.GetEnvironmentVariable("COSMOSDB_CONNECTIONSTRING"));
});
builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
}
}
}
GitHub Example
Documentation
As stated above, it was just announced at Build 2019. It can now be setup almost exactly like you would in an ASP .Net Core app.
Microsoft Documentation
Short Blog I Wrote
Actually there is a much nicer and simpler way provided out of the box by Microsoft. It is a bit hard to find though. You simply create a start up class and add all required services here, and then you can use constructor injection like in regular web apps and web apis.
This is all you need to do.
First I create my start up class, I call mine Startup.cs to be consistent with Razor web apps, although this is for Azure Functions, but still it's the Microsoft way.
using System;
using com.paypal;
using dk.commentor.bl.command;
using dk.commentor.logger;
using dk.commentor.sl;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using org.openerp;
[assembly:Microsoft.Azure.WebJobs.Hosting.WebJobsStartup(typeof(dk.commentor.starterproject.api.Startup))]
namespace dk.commentor.starterproject.api
{
public class Startup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
builder.Services.AddSingleton<ILogger, CommentorLogger>();
builder.Services.AddSingleton<IPaymentService, PayPalService>();
builder.Services.AddSingleton<IOrderService, OpenERPService>();
builder.Services.AddSingleton<ProcessOrderCommand>();
Console.WriteLine("Host started!");
}
}
}
Next I change the method call in the function from static to non-static, and I add a constructor to the class (which is now also non-static). In this constructor I simply add the services I require as constructor parameters.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using dk.commentor.bl.command;
namespace dk.commentor.starterproject.api
{
public class ProcessOrder
{
private ProcessOrderCommand processOrderCommand;
public ProcessOrder(ProcessOrderCommand processOrderCommand) {
this.processOrderCommand = processOrderCommand;
}
[FunctionName("ProcessOrder")]
public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger ProcessOrder called!");
log.LogInformation(System.Environment.StackTrace);
string jsonRequestData = await new StreamReader(req.Body).ReadToEndAsync();
dynamic requestData = JsonConvert.DeserializeObject(jsonRequestData);
if(requestData?.orderId != null)
return (ActionResult)new OkObjectResult($"Processing order with id {requestData.orderId}");
else
return new BadRequestObjectResult("Please pass an orderId in the request body");
}
}
}
Hopes this helps.
I would like to add my 2 cents to it. I used the technique that it's used by Host injecting ILogger. If you look at the Startup project I created GenericBindingProvider that implements IBindingProvider. Then for each type I want to be injected I register it as follow:
builder.Services.AddTransient<IWelcomeService, WelcomeService>();
builder.Services.AddSingleton<IBindingProvider, GenericBindingProvider<IWelcomeService>>();
The downside is that you need to register the type you want to be injected into the function twice.
Sample code:
Azure Functions V2 Dependency Injection sample
I have been using SimpleInjector perfectly fine in Azure Functions. Just create a class (let's call it IoCConfig) that has the registrations and make a static instance of that class in function class so that each instance will use the existing instance.
public interface IIoCConfig
{
T GetInstance<T>() where T : class;
}
public class IoCConfig : IIoCConfig
{
internal Container Container;
public IoCConfig(ExecutionContext executionContext, ILogger logger)
{
var configurationRoot = new ConfigurationBuilder()
.SetBasePath(executionContext.FunctionAppDirectory)
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
Container = new Container();
Configure(configurationRoot, logger);
}
public IoCConfig(IConfigurationRoot configurationRoot, ILogger logger)
{
Container = new Container();
Configure(configurationRoot, logger);
}
private void Configure(IConfigurationRoot configurationRoot, ILogger logger)
{
Container.RegisterInstance(typeof(IConfigurationRoot), configurationRoot);
Container.Register<ISomeType, SomeType>();
}
public T GetInstance<T>() where T : class
{
return Container.GetInstance<T>();
}
}
Then in root:
public static class SomeFunction
{
public static IIoCConfig IoCConfig;
[FunctionName("SomeFunction")]
public static async Task Run(
[ServiceBusTrigger("some-topic", "%SUBSCRIPTION_NAME%", Connection = "AZURE_SERVICEBUS_CONNECTIONSTRING")]
SomeEvent msg,
ILogger log,
ExecutionContext executionContext)
{
Ensure.That(msg).IsNotNull();
if (IoCConfig == null)
{
IoCConfig = new IoCConfig(executionContext, log);
}
var someType = IoCConfig.GetInstance<ISomeType>();
await someType.Handle(msg);
}
}
AzureFunctions.Autofac is very easy to use.
Just add a config file:
public class DIConfig
{
public DIConfig(string functionName)
{
DependencyInjection.Initialize(builder =>
{
builder.RegisterType<Sample>().As<ISample>();
...
}, functionName);
}
}
Add the DependencyInjectionConfig attribute then inject:
[DependencyInjectionConfig(typeof(DIConfig))]
public class MyFunction
{
[FunctionName("MyFunction")]
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = null)]HttpRequestMessage request,
TraceWriter log,
[Inject]ISample sample)
{
https://github.com/introtocomputerscience/azure-function-autofac-dependency-injection
I think this is a better solution:
https://github.com/junalmeida/autofac-azurefunctions
https://www.nuget.org/packages/Autofac.Extensions.DependencyInjection.AzureFunctions
Install the NuGet in your project and then make a Startup.cs and put this in it:
[assembly: FunctionsStartup(typeof(Startup))]
public class Startup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder
.UseAppSettings() // this is optional, this will bind IConfiguration in the container.
.UseAutofacServiceProviderFactory(ConfigureContainer);
}
private void ConfigureContainer(ContainerBuilder builder)
{
// do DI registration against Autofac like normal! (builder is just the normal ContainerBuilder from Autofac)
}
...
Then in your function code you can do normal constructor injection via DI:
public class Function1 : Disposable
{
public Function1(IService1 service1, ILogger logger)
{
// logger and service1 injected via autofac like normal
// ...
}
[FunctionName(nameof(Function1))]
public async Task Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")]string myQueueItem)
{
//...
Support for Dependency injection begins with Azure Functions 2.x which means Dependency Injection in Azure function can now leverage .NET Core Dependency Injection features.
Before you can use dependency injection, you must install the following NuGet packages:
Microsoft.Azure.Functions.Extensions
Microsoft.NET.Sdk.Functions
Having Dependency Injection eases things like DBContext, Http client usage (Httpclienfactory), Iloggerfactory, cache support etc.
Firstly, update the Startup class as shown below
namespace DemoApp
{
public class Startup: FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddScoped<IHelloWorld, HelloWorld>();
// Registering Serilog provider
var logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
builder.Services.AddLogging(lb => lb.AddSerilog(logger));
//Reading configuration section can be added here etc.
}
}
}
Secondly, Removal of Static keyword in Function class and method level
public class DemoFunction
{
private readonly IHelloWorld _helloWorld;
public DemoFunction(IHelloWorld helloWorld)
{
_helloWorld = helloWorld;
}
[FunctionName("HttpDemoFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
}
If we look into above e.g. IHelloWorld is injected using .NET Core DI
**Note:**In-spite of having latest version of Azure function v3 for Dependency Injection to enable few steps are manual as shown above
Sample code on github can be found here

Flex + BlazeDS + Multi module maven project

I've got a multi module Maven project (about 10 modules) where 2 of the modules are a flex project and its corresponding server project, communicating via BlazeDS.
The server module is dependent on another module containing common things, shared over the whole project. When using objects from the common module, the objects aren't serialized and sent via AMF to the SWF. Everything in the server-module is serialized and is working fine, but the objects from the common module (which has valid values on the server side) is not sent to the client.
I'm using Flexmojos to build this. What do I have to do to make the classes in the common project available for serialization, and being able to use them as RemoteClass-objects in my swf-project?
The basic structure is similar to this (I've tried so simplify it quite a bit):
swf-module (Flex):
Class MyObject.as:
package swf.model {
[RemoteClass(alias="server.model.MyObject")]
public class MyObject {
public var name:String;
public var common:MyCommonObject;
}
}
Class MyCommonObject.as:
package swf.model {
[RemoteClass(alias="common.model.MyCommonObject")]
public class MyCommonObject {
public var commonNumber:Number; }
}
server-module (Java):
Class MyObject.java:
package server.model;
import common.model.MyCommonObject;
public class MyObject {
private String name;
private MyCommonObject common;
public MyObject() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public MyCommonObject getCommon() {
return common;
}
public void setCommon(MyCommonObject common) {
this.common= common;
}
}
common-module (Java)
Class MyCommonObject.java:
package common.model;
public class MyCommonObject{
private Double commonNumber;
public MyCommonObject() {}
public Double getCommonNumber() {
return commonNumber;
}
public void setCommonNumber(Double commonNumber) {
this.commonNumber= commonNumber;
}
}
Java server side DTOs and ActionScript client DTOs are independent. I mean the following. When your BlazeDS services return AMF-serialized DTOs their binary structure is described by AMF format. And AMF transfer data contains full classpath which you describe on a client side using RemoteClass metadata. In this way client Flex project and Java server project haven't dependencies on each other in process of building. But you can build them together to produce the same WAR which contains both client and server part.
I have actually had to do this, you can go here, get the source for BlazeDS, add it to your project and debug to your heart's content.
I Think your common-module JAR is not in classpath of Flex module/WAR/BlazeDS,
try to put common module JAR in Flex modules war
means PUT {common module}.jar in {BlazeDS}\WEB-INF\lib\ on deployment
if its not there.
Hopes it works

Resources