How To: Caliburn.Micro.Autofac and Windows Phone - windows-phone-7

Is there and example, tutorial or anything that shows how to use Caliburn.Micro.Autofac with Windows Phone?
I created a basic application with Caliburn.Micro only, and that runs fine. Then I decided to use Caliburn.Micro.Autofac, so I derived my Bootstrapper from Caliburn.Micro.Autofac.AutofacBootstrapper and called base.Configure() inside the Bootstrapper Configure() method. Now wen I ran the application I get "The type 'AppBootstrapper' was not found." exception.
Appreciate any help.

This is the bootstrapper I wrote for a WP7 project. It's based on Caliburn.Micro.Autofac.AutofacBootstrapper but fixes some bugs.
public class AppBootstrapper : PhoneBootstrapper
{
private IContainer container;
protected void ConfigureContainer(ContainerBuilder builder)
{
// put any custom bindings here
}
#region Standard Autofac/Caliburn.Micro Bootstrapper
protected override void Configure()
{
// configure container
var builder = new ContainerBuilder();
// register phone services
var caliburnAssembly = AssemblySource.Instance.Union(new[] { typeof(IStorageMechanism).Assembly }).ToArray();
// register IStorageMechanism implementors
builder.RegisterAssemblyTypes(caliburnAssembly)
.Where(type => typeof(IStorageMechanism).IsAssignableFrom(type)
&& !type.IsAbstract
&& !type.IsInterface)
.As<IStorageMechanism>()
.SingleInstance();
// register IStorageHandler implementors
builder.RegisterAssemblyTypes(caliburnAssembly)
.Where(type => typeof(IStorageHandler).IsAssignableFrom(type)
&& !type.IsAbstract
&& !type.IsInterface)
.As<IStorageHandler>()
.SingleInstance();
// The constructor of these services must be called
// to attach to the framework properly.
var phoneService = new PhoneApplicationServiceAdapter(RootFrame);
var navigationService = new FrameAdapter(RootFrame, false);
builder.Register<IPhoneContainer>(c => new AutofacPhoneContainer(c)).SingleInstance();
builder.RegisterInstance<INavigationService>(navigationService).SingleInstance();
builder.RegisterInstance<IPhoneService>(phoneService).SingleInstance();
builder.Register<IEventAggregator>(c => new EventAggregator()).SingleInstance();
builder.Register<IWindowManager>(c => new WindowManager()).SingleInstance();
builder.Register<IVibrateController>(c => new SystemVibrateController()).SingleInstance();
builder.Register<ISoundEffectPlayer>(c => new XnaSoundEffectPlayer()).SingleInstance();
builder.RegisterType<StorageCoordinator>().AsSelf().SingleInstance();
builder.RegisterType<TaskController>().AsSelf().SingleInstance();
// allow derived classes to add to the container
ConfigureContainer(builder);
// build the container
container = builder.Build();
// start services
container.Resolve<StorageCoordinator>().Start();
container.Resolve<TaskController>().Start();
// add custom conventions for the phone
AddCustomConventions();
}
protected override object GetInstance(Type service, string key)
{
object instance;
if (string.IsNullOrEmpty(key))
{
if (container.TryResolve(service, out instance))
return instance;
}
else
{
if (container.TryResolveNamed(key, service, out instance))
return instance;
}
throw new Exception(string.Format("Could not locate any instances of contract {0}.", key ?? service.Name));
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return container.Resolve(typeof(IEnumerable<>).MakeGenericType(service)) as IEnumerable<object>;
}
protected override void BuildUp(object instance)
{
container.InjectProperties(instance);
}
private static void AddCustomConventions()
{
ConventionManager.AddElementConvention<Pivot>(Pivot.ItemsSourceProperty, "SelectedItem", "SelectionChanged").ApplyBinding =
(viewModelType, path, property, element, convention) =>
{
if (ConventionManager
.GetElementConvention(typeof(ItemsControl))
.ApplyBinding(viewModelType, path, property, element, convention))
{
ConventionManager
.ConfigureSelectedItem(element, Pivot.SelectedItemProperty, viewModelType, path);
ConventionManager
.ApplyHeaderTemplate(element, Pivot.HeaderTemplateProperty, viewModelType);
return true;
}
return false;
};
ConventionManager.AddElementConvention<Panorama>(Panorama.ItemsSourceProperty, "SelectedItem", "SelectionChanged").ApplyBinding =
(viewModelType, path, property, element, convention) =>
{
if (ConventionManager
.GetElementConvention(typeof(ItemsControl))
.ApplyBinding(viewModelType, path, property, element, convention))
{
ConventionManager
.ConfigureSelectedItem(element, Panorama.SelectedItemProperty, viewModelType, path);
ConventionManager
.ApplyHeaderTemplate(element, Panorama.HeaderTemplateProperty, viewModelType);
return true;
}
return false;
};
}
#endregion
}
EDIT I have created a fork of Caliburn.Micro.Autofac and fixed the issue on GitHub. Hopefully the pull request will be accepted and this will become part of the main repository.
For now, you can access the bootstrapper, and AutofacPhoneContainer from here - https://github.com/distantcam/Caliburn.Micro.Autofac/tree/master/src/Caliburn.Micro.Autofac-WP7

I have implemented a proper version (in my opinion) of Caliburn.Micro.Autofac for Windows Phone. You can download it and test project from my blog. The blog post is in Russian but you'll find the link to ZIP file in the top of the post. The code is too big to post here, so please take from the blog. I've send this to David Buksbaum (the author of Caliburn.Micro.Autofac). Hope he will incorporate it into his code base soon.
UPDATE
What is fixed:
Components realizing IPhoneService and INavigationService services must be instantiated before registering in container.
Realized component implementing IPhoneContainer. Without it you can't use Autofac in Caliburn.Micro.

Related

Windows 10 version 1903 update (NET Framework 4.8) breaking Prism 6.3

First off, we are well aware that this isn't strictly speaking a Prism 6.3 issue; what we're looking for (in case a solution isn't straightforward) are pointers to a solution to the problem -thanks in advance btw-, which is:
Windows 10 version 1903, via .NET Framework 4.8 inclusion, breaks our otherwise perfectly functioning, tried-and-true, production-deployed Prism 6.3-based commercial software. We're using Prism (Core), Prism.WPF, and Prism.MEF (all v6.3). What we get (source code later) is the following runtime error whenever we try to instantiate a registered View:
Prism.Regions.RegionNavigationService.CreateNewRegionItem(String candidateTargetContract) throws an InvalidOperationException: Cannot create navigation target 'xyzView'. Activation error ocurred while tring to get instance of type Object, key 'xyzView'.
Inner exception stems from Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key), which throws an ActivationException: Activation error ocurred while tring to get instance of type Object, key 'xyzView'.
Devil's in the details so here's some relevant code (xyzView = ExpedicionView or ExpedicionMaestroView, both trigger the Exception):
[ModuleExport(typeof(ExpedicionModulo))]
[Export(typeof(IMenu))]
public class ExpedicionModulo : IModule, IMenu
{
[Import]
public IRegionManager RegionManager;
[Import]
public ILoggerFacade Logger;
[ImportingConstructor]
public ExpedicionModulo(IRegionManager regionManager, ILoggerFacade logger)
{
Logger = logger;
RegionManager = regionManager;
// irrelevant (for our purposes) code omitted here
}
public void Initialize()
{
// Here's how we register views for main region
RegionManager.RegisterViewWithRegion(RegionNames.MainContentRegion, typeof(ExpedicionView));
RegionManager.RegisterViewWithRegion(RegionNames.MainContentRegion, typeof(MaestroExpedicionView));
// Some more registering for our dialog region
RegionManager.RegisterViewWithRegion(RegionNames.DialogRegion, typeof(ExpedicionDetalleView));
// other views registered in the very same fashion
Logger.Log("Expedition Module initialized", Category.Info, Priority.None);
}
We invoke RequestNavigate in this bit of code here:
[Export]
[PartCreationPolicy(CreationPolicy.Shared)]
public class ShellViewModel : BaseViewModel
{
public IRibbonPrincipal RibbonPrincipal { get; set; }
private readonly InteractionRequest<Confirmation> _confirmationInteractionRequest;
private readonly InteractionRequest<Notification> _notificationInteractionRequest;
private string _seccionActiva;
private string _subseccionActiva;
private IRegionManager _regionManager;
[ImportingConstructor]
public ShellViewModel(IRegionManager regionManager, IEventAggregator eventAggregator, ILoggerFacade logger)
: this(eventAggregator, logger)
{
try
{
_regionManager = regionManager;
_regionManager.RegisterViewWithRegion(RegionNames.MainContentRegion, typeof(PrincipalView));
}
catch (Exception ex)
{
this.LoggerError(ex.Message);
}
}
public ShellViewModel(IEventAggregator eventAggregator, ILoggerFacade logger)
: base(eventAggregator, logger)
{
try
{
// Some irrelevant (to our purposes) initialization code omitted here
New = new DelegateCommand(() => { }, () => { return false; }); // etc.
_confirmationInteractionRequest = new InteractionRequest<Confirmation>();
_notificationInteractionRequest = new InteractionRequest<Notification>();
// Events code omitted for brevity sake
EventAggregator.GetEvent<MessageBoxEvent>().Subscribe(ShowMessageBox, ThreadOption.PublisherThread, false); // etc.
// View loading wireup
CambioSeccion = new DelegateCommand<object>(OnCambioSeccion);
CambioSubseccion = new DelegateCommand<object[]>(OnCambioSubseccion);
// some more irrelevant (to our purposes) code omitted here.
}
catch (Exception ex)
{
this.LoggerError(ex.Message);
}
}
/// <summary>
/// This is where we instantiate the View (WAI in .NET Framework <=4.7.2)
/// </summary>
/// <param name="objeto"></param>
private void OnCambioSeccion(object objeto)
{
// Ribbon menu handling here
RibbonPrincipal.ResetRibbon();
IIdentifyViewModel ivm = null;
// We are passing the View's name via clicked TreeViewItem in this case (param objeto)
TreeViewItem treeViewItem = (TreeViewItem)objeto;
if (treeViewItem != null && !string.IsNullOrEmpty(treeViewItem.Name))
{
_seccionActiva = treeViewItem.Name;
// Now we build the actual RequestNavigate invoke
// In our case, _seccionActiva would equal "Expedicion" or "MaestroExpedicion"
_regionManager.RequestNavigate(RegionNames.MainContentRegion, new Uri("/" + _seccionActiva + "View", UriKind.Relative), (r) =>
{
if (!r.Result.HasValue || !r.Result.Value)
{
// error handling code here
}
else
{
ivm = ((FrameworkElement)_regionManager.Regions[RegionNames.MainContentRegion].ActiveViews.First()).DataContext as IIdentifyViewModel;
}
});
// Some event triggering here
if (ivm != null)
{
Seccion.Cambio(EventAggregator, ivm.ID);
}
}
}
Sorry for the long winded post, and thanks in advance.
#mcandal thanks for the write-up. This looks like a recent issue introduced in .NET Framework 4.8 that we have seen with the patterns and practices activation code. The underlying issue is that in 4.8 the constructors for types were returned in a different order resulting in the patterns and practices choice of the first (eg. ctors[0]) sometimes no longer being correct.
There are a few workarounds as we work through a fix in an upcoming release.
This issue only impacts assemlbies that are ngen'd.
Workarounds:
1) Disable ngen for the assembly containing the type:
<configuration>
<runtime>
<disableNativeImageLoad>
<assemblyIdentity name="assembly_name" />
</disableNativeImageLoad>
</runtime>
</configuration>
2) For the type that is being activated only have 1 ctor (the documentation for the patterns and practices assumes there is only 1 ctor)
3) Choose the other provided activation models where you pass in the types of the parameters, to avoid the ambiguity.
As I mentioned, we are working on a fix and if you would like to discuss further you can submit a VS Feedback item and link to this post and we can continue the discussion there.

Dependency injection in Masstransit routing slip Activity

I working on a project using masstransit Courier. I am trying to inject some service dependencies into my Activities but this is not working. Can someone help how to use DI in MT activity. I could not find any working example with Activity implementation and DI
In my below code I am trying to inject IMyservice into my activity. But my activity constructor is never executing. Am I doing something wrong here?
public class DownloadImageActivity :
Activity<DownloadImageArguments, DownloadImageLog>
{
public DownloadImageActivity (IMyService service)
{
_service=service;
}
Task<ExecuteResult> Execute(ExecutionContext<DownloadImageArguments> context);
Task<CompensationResult> Compensate(CompensateContext<DownloadImageLog> context);
}
my service program code ActivityService.cs
_busControl = Bus.Factory.CreateUsingRabbitMq(x =>
{
IRabbitMqHost host = x.Host(new Uri(ConfigurationManager.AppSettings["RabbitMQHost"]), h =>
{
h.Username("guest");
h.Password("guest");
});
x.ReceiveEndpoint(host, ConfigurationManager.AppSettings["completepitaskqueue"], e =>
{
e.PrefetchCount = 100;
e.ExecuteActivityHost<CompletePiTaskActivity, ComplePiTaskArguments>(Program.Container);
// e.ExecuteActivityHost(
// DefaultConstructorExecuteActivityFactory<CompletePiTaskActivity, ComplePiTaskArguments>.ExecuteFactory, c => c.UseRetry(r => r.Immediate(5)));
});
My program.cs
public static IContainer Container;
static int Main(string[] args)
{
ConfigureLogger();
Container = builder.Build();
ActivityHelper.Container = Container;
// MassTransit to use Log4Net
Log4NetLogger.Use();
return (int)HostFactory.Run(x => x.Service<ActivityService>());
It depends upon which container you are using, some are more supported than others (out of the box, anyway, any should work).
For example, to configure a receive endpoint with an activity using Autofac, you would specify:
cfg.ReceiveEndpoint(host, "execute-activity", e =>
{
e.ExecuteActivityHost<TActivity, TArguments>(container);
});
Where container is either the builder context or the container. This will register the appropriate scope providers and activity factories for your activity.
This uses the extension method: https://github.com/MassTransit/MassTransit/blob/develop/src/Containers/MassTransit.AutofacIntegration/AutofacExtensions.cs#L300

Requesting Android permissions in a class (Xamarin)

I'm trying to request a permission at runtime for my app. I use a service provider to talk between the portable class and Android.
I start by calling this code on button press in the PCL:
using (new Busy(this))
{
var locationHelper = scope.Resolve<ILocationHelper>();
locationHelper.GetLocation(this);
}
This calls my Android level service:
public class AndroidLocationHelper : ILocationHelper, ILocationListener
{
readonly string[] PermissionsLocation =
{
Manifest.Permission.AccessCoarseLocation
};
const int RequestLocationId = 0;
public void GetLocation(SearchViewModel viewModel)
{
try
{
const string permission = Manifest.Permission.AccessCoarseLocation;
if (((int)Build.VERSION.SdkInt < 23) || (CheckSelfPermission(permission) == Permission.Granted))
{
}
else
RequestPermissions(PermissionsLocation, RequestLocationId);
}
catch (Exception ex)
{
Debug.WriteLine("Error while getting Location service");
Debug.WriteLine(ex.Message);
Messaging.AlertUser("There was an error with determining your location");
}
}
However, I get two errors on CheckSelfPermission and RequestPermissions. These two methods are only available to activities. The code works fine in MainActivity; however, I want to ask for permissions when the user hits a button, not in OnCreate or OnResume, etc.
Thanks for any help.
In your Android project, You can use this and use the Dependency Service to call it in Xamarin.Forms PCL project later:
var thisActivity = Forms.Context as Activity;
ActivityCompat.RequestPermissions(thisActivity, new string[] {
Manifest.Permission.AccessFineLocation }, 1);
ActivityCompat.RequestPermissions(thisActivity,
new String[] { Manifest.Permission.AccessFineLocation },
1);
You can try with ContextCompat.CheckSelfPermission, passing the application context, like this:
ContextCompat.CheckSelfPermission(Android.App.Application.Context, permission)
Update
In case of ActivityCompat.RequestPermissions, which requires an activity reference, you can keep track of the current activity. There is a very handy lib for that, called "CurrentActivityPlugin". You can find at https://github.com/jamesmontemagno/CurrentActivityPlugin
Rafael came up with a solution but I found another option that is a lot less effort just using MessagingCenter. In the MainActivity's OnCreate add a receiver that runs all the location code, that way you have access to all of the activities methods (and there are a bunch of tutorials on doing location services in MainActivity). Then add the Send inside of your service (the class).
To expound Rafael Steil's answer, I tried the suggested CurrentActivityPlugin and it worked on me. In my case I am trying to execute a voice call which needs CALL_PHONE permission. Here is the code snippet in your case: I used the ContextCompat & ActivityCompat so that I don't need to check the VERSION.SdkInt
using Plugin.CurrentActivity;
public void GetLocation(SearchViewModel viewModel){
var context = CrossCurrentActivity.Current.AppContext;
var activity = CrossCurrentActivity.Current.Activity;
int YOUR_ASSIGNED_REQUEST_CODE = 9;
if (ContextCompat.CheckSelfPermission(context, Manifest.Permission.AccessCoarseLocation) == (int)Android.Content.PM.Permission.Granted)
{
//Permission is granted, execute stuff
}
else
{
ActivityCompat.RequestPermissions(activity, new string[] { Manifest.Permission.AccessCoarseLocation }, YOUR_ASSIGNED_REQUEST_CODE);
}
}
It's dead simple
public bool CheckPermission()
{
const string permission = Manifest.Permission.ReceiveSms;
return ContextCompat.CheckSelfPermission(Forms.Context, permission) == (int) Permission.Granted;
}

Application_End equivalent in ASP.NET Core rc2? [duplicate]

Is there a shutdown function when using Microsoft.AspNet.Server.Kestrel? ASP.NET Core (formerly ASP.NET vNext) clearly has a Startup sequence, but no mention of shutdown sequence and how to handle clean closure.
In ASP.NET Core you can register to the cancellation tokens provided by IApplicationLifetime
public class Startup
{
public void Configure(IApplicationBuilder app, IApplicationLifetime applicationLifetime)
{
applicationLifetime.ApplicationStopping.Register(OnShutdown);
}
private void OnShutdown()
{
// Do your cleanup here
}
}
IApplicationLifetime is also exposing cancellation tokens for ApplicationStopped and ApplicationStarted as well as a StopApplication() method to stop the application.
For .NET Core 3.0+
From comments #Horkrine
For .NET Core 3.0+ it is recommended to use IHostApplicationLifetime instead, as IApplicationLifetime will be deprecated soon. The rest will still work as written above with the new service
In addition to the original answer, I had an error while trying to wire the IApplicationLifetime within the constructor.
I solved this by doing:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
var applicationLifetime = app.ApplicationServices.GetRequiredService<IApplicationLifetime>();
applicationLifetime.ApplicationStopping.Register(OnShutdown);
}
private void OnShutdown()
{
// Do your cleanup here
}
}
I solved it with the application lifetime callback events
Startup.cs
public void Configure(IHostApplicationLifetime appLifetime) {
appLifetime.ApplicationStarted.Register(() => {
Console.WriteLine("Press Ctrl+C to shut down.");
});
appLifetime.ApplicationStopped.Register(() => {
Console.WriteLine("Terminating application...");
System.Diagnostics.Process.GetCurrentProcess().Kill();
});
}
Program.cs
Also, use UseConsoleLifetime() while building the host.
Host.CreateDefaultBuilder(args).UseConsoleLifetime(opts => opts.SuppressStatusMessages = true);
This class is now obsolete, please refer to the new interface IHostApplicationLifetime. More info here.
When using .NET 6 without a Startup class, you can access the ApplicationStopping CancellationToken via the built web application in Program.cs, e.g.
var app = builder.Build();
...
app.Lifetime.ApplicationStopping.Register(() => ...);
...
app.Run();
In .Net 6 if you are not using Startup.cs. You could use ApplicationStopping method in Program.cs
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ContentRootPath = Directory.GetCurrentDirectory(),
WebRootPath = "wwwroot"
});
var app = builder.Build();
app.Lifetime.ApplicationStopping.Register(() =>
{
// Do Something
});
app.Run();

Adding profile values for auto-generated user

I'm creating a ASP.NET MVC 3.0 website, and have a couple of different database initializations based on whether the site is intended for development, testing, or production. I'm stuck on the testing initialization, as I'm trying to get a test user created. I can get the user to create just fine, however when I try to add some profile values, I get: System.Web.HttpException: Request is not available in this context. Is there a way to add Profile values in a situation where the request isn't going to be available?
Following code is what is being run:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
if (ApplicationServices.GetInitialCatalog() != "tasktracker")
{
Database.SetInitializer(new TaskTrackerDropCreateDatabaseIfModelChanges());
}
else
{
Database.SetInitializer(new TaskTrackerCreateDatabaseIfNotExists());
}
using (var db = new TaskTrackerContext())
{
db.Database.Initialize(false);
}
}
public class TaskTrackerDropCreateDatabaseIfModelChanges : DropCreateDatabaseIfModelChanges<TaskTrackerContext>
{
protected override void Seed(TaskTrackerContext context)
{
// Set up the membership, roles, and profile systems.
ApplicationServices.InstallServices(SqlFeatures.Membership | SqlFeatures.Profile | SqlFeatures.RoleManager);
// Create the default accounts and roles.
if (ApplicationServices.GetInitialCatalog() == "tasktracker_testing")
{
if (Membership.GetUser("testuser", false) == null)
{
Membership.CreateUser("testuser", "password", "testuser#test.com");
MembershipUser user = Membership.GetUser("testuser", false);
user.IsApproved = true;
var profile = ProfileBase.Create("testuser");
profile.SetPropertyValue("FirstName", "test");
profile.SetPropertyValue("LastName", "user");
profile.SetPropertyValue("TimeZone", "US Mountain Standard Time");
profile.Save();
}
}
}
}
Interesting question. Have you looked at using the new Universal Providers? Dunno if you will run into the same httpcontext issue but may be worth a look: http://www.hanselman.com/blog/IntroducingSystemWebProvidersASPNETUniversalProvidersForSessionMembershipRolesAndUserProfileOnSQLCompactAndSQLAzure.aspx
Did you try to do a call of "Initialize()" :
profile.Initialize(username, true)
after your create action to see if the context should be Initialized.
By using Reflector i saw the ProfileBase of Initialize (see below) creates this kind of context from the settings:
public void Initialize(string username, bool isAuthenticated)
{
if (username != null)
{
this._UserName = username.Trim();
}
else
{
this._UserName = username;
}
SettingsContext context = new SettingsContext();
context.Add("UserName", this._UserName);
context.Add("IsAuthenticated", isAuthenticated);
this._IsAuthenticated = isAuthenticated;
base.Initialize(context, s_Properties, ProfileManager.Providers);
}
It seems working here, the SettingsContext() seems taking account of my custom properties declared in the web.config.
Regards,
I come back again because the solution I added with the "Initialize()" function in fact not run really after an other test. So in fact I found a way which runs correctly.
The problem of "request is not available in this context" in application_start in your case could be due to the application mode "Integrated" which is new from II7 instead of the Classic mode.
To see a good explain you ca go on the Mike Volodarsky's blog IIS7 Integrated mode: Request is not available in this context exception in Application_Start .
I copy/paste an extract which could indicate the main reason:
" *This error is due to a design change in the IIS7 Integrated pipeline that makes the request context unavailable in Application_Start event. When using the Classic mode (the only mode when running on previous versions of IIS), the request context used to be available, even though the Application_Start event has always been intended as a global and request-agnostic event in the application lifetime. Despite this, because ASP.NET applications were always started by the first request to the app, it used to be possible to get to the request context through the static HttpContext.Current field.* "
To solve this you can use a workaround that moves your first-request initialization from Application_Start to BeginRequest and performs the request-specific initialization on the first request.
A good example of code is done in his blog :
void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
HttpContext context = app.Context;
// Attempt to peform first request initialization
FirstRequestInitialization.Initialize(context);
}
class FirstRequestInitialization
{
private static bool s_InitializedAlready = false;
private static Object s_lock = new Object();
// Initialize only on the first request
public static void Initialize(HttpContext context)
{
if (s_InitializedAlready)
{
return;
}
lock (s_lock)
{
if (s_InitializedAlready)
{
return;
}
// Perform first-request initialization here
//
// You can use your create profile code here....
//---
s_InitializedAlready = true;
}
}
}

Resources