I am building a simple application with 4-5 views in Silverlight. I came across MVVM Light toolkit and I think it suits my need.
Background
Application will have views with typical list and details display
Manufacturer
Product
and so on with left navigation, header and footer (User controls).
I am thinking of having a main page with user controls created at design time.
Problem
On selection of links from left navigation control, the central panel should be updated with a different view (like Manufacturer, product and so on)
I understand that Messenger is an option to communicate between different VMs in light toolkit.
Question
How can I design my app with MVVM light toolkit. Central pane need to be loaded with a different view at runtime.
I am particularly looking at help in implementing the navigation portion of the application.
Thank you.
I had to implement basic nagivigtion in an NON mvvm way. I have a message listener sitting on the constructor of my main view that listens for a page navigation message(custom message learn it, love it,use it)then it sets the content source of the nav frame to the url that is sent in the message. I have the URLs for all my page and subpage navigation setup using string constants.
public MainPage()
{
InitializeComponent();
Loaded += OnLoaded;
WebContext.Current.Authentication.LoggedOut +=
new EventHandler<System.ServiceModel.DomainServices.Client.ApplicationServices.AuthenticationEventArgs>(Authentication_LoggedOut);
Messenger.Default.Register<msgs.NavigationRequest<PageURI>>(this, (uri => ContentFrame.Navigate(uri.Content)));
Messenger.Default.Register<WavelengthIS.Core.Messaging.ExceptionMessage>(this, ex => ShowExceptionMessage(ex));
Messenger.Default.Register<WavelengthIS.Core.Messaging.StringMessage>(this, str => ShowMessageForUser(str));
}
public class PageURI : Uri
{
public PageURI(string uriString, UriKind uriKind)
: base(uriString, uriKind)
{
}
}
public class PageLinks
{
public const string SEARCHBYDAYCOUNTVIEW = "/Views/PatientSearchHeaders/SearchByDayCountView.xaml";
public const string SEARCHBYPATIENTCRITERIAVIEW = "/Views/PatientSearchHeaders/SearchByPatientCriteriaView.xaml";
public const string QUESTIONAIRRESHELL = "/Views/QuestionairreViews/QuestionairreShell.xaml";
public const string HOME = "/Views/PrimarySearchView.xaml";
public const string REPORTS = "/Views/ReportsPage.xaml";
public const string LOGINPAGE = "/Views/LoginPageView.xaml";
}
Actual Calling in VM:
private void OnSurveyCommandExecute()
{
Wait.Begin("Loading Patient List...");
_messenger.Send<ReadmitPatientListViewModel>(this);
_messenger.Send<Messages.NavigationRequest<SubClasses.URI.PageURI>>(GetNavRequest_QUESTIONAIRRESHELL());
}
private static Messages.NavigationRequest<SubClasses.URI.PageURI> GetNavRequest_QUESTIONAIRRESHELL()
{
Messages.NavigationRequest<SubClasses.URI.PageURI> navRequest =
new Messages.NavigationRequest<SubClasses.URI.PageURI>(
new SubClasses.URI.PageURI(Helpers.PageLinks.QUESTIONAIRRESHELL, System.UriKind.Relative));
return navRequest;
}
Related
I'm starting a new ASP.NET project after a few years developing in MVC4, and I have a question regarding architecture.
At the top corner of each page, I will display details of the current logged in user.
In MVC4 I achieved something like this by creating a BaseController, which created an EF data connection, and set up some common variables that would be used on every page - CurrentUser being one of them.
Now that I'm using Core, this approach doesn't seem to work, and certainly isnt mockable.
What would be the correct way to achieve something like this via ASP.NET Core?
I need the same variables on every view, and certainly dont want to have to write the code in each controller action!
You can use View Components feature in asp.net core to implement that functionality.
//In your ConfigureServices method , add your services that will be injected whenever view component is instantiated
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton<IUserRespository, UserRepository>();
}
//Now Create a view component
public class LoggedInUser : ViewComponent
{
private IUserRespository userRepository;
//Services can be injected using asp.net core DI container
public LoggedInUser(IUserRepository userRepository,SomeOtherService service)
{
//assign services to local variable for use later
this.userRepository = userRepository;
}
//This method can take any number of parameters and returns view
public async Task<IViewComponentResult> InvokeAsync(int param1,string param2,etc )
{
//get the logged in user data here using available services
var loggedInUserData = GetSomeData(context);
return View(loggedInUserData );
}
}
Create view file # View/Shared/Components/LoggedInUser/Default.cshtml.View can be strongly typed.
#model LoggedInUserModel
<div>
<!-- html here to render model -->
</div>
Now, since you use to display this data on every page , you need to apply _Layout.chstml to all your pages . In the _Layout.chstml , you can render view component defined above with any additional parameter you would like to pass as anonymous type.
#await Component.InvokeAsync("LoggedInUser", new { param1=value,param2=value,etc })
Testing the View Component:
var mockRepository = Mock of ICityRepository;
var viewComponent= new LoggedInUser(mockRepository);
ViewViewComponentResult result
= viewComponent.Invoke() as ViewViewComponentResult; //using Invoke here instead of InvokeAsnyc for simplicity
//Add your assertions now on result
Note :
It is also possible to decorate a controller with [ViewComponent(Name = "ComponentName")] attribute and define public IViewComponentResult Invoke()
or public IViewComponentResult InvokeAsync() to turn them in to hybrid controller - view component.
I would like some feedback to see if I'm using SimpleIoc in the correct way.
The code below works, but I'm not sure if it's best practice.
I have an UWP XAML DocumentPage class on which I want to show an IRpcDocument.
I want to use the DocumentPage for both RpcDocumentA and RpcDocumentB. The user can navigate to both types of IRpcDocument. So the application should be able to switch between the two 'on the fly'.
So I wrote my DocumentPageViewModel
public class DocumentPageViewModel : ViewModelBase
{
public IRpcDocument RpcDocument;
public DocumentPageViewModel(IRpcDocument rpcDocument)
{
RpcDocument = rpcDocument;
}
}
And my ViewModelLocator
class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<DocumentPageViewModel>();
}
public DocumentPageViewModel SimpleIoc.Default.Register<DocumentPageViewModel>
{
get
{
return ServiceLocator.Current.GetInstance<SimpleIoc.Default.Register<DocumentPageViewModel>>(Guid.NewGuid().ToString());
}
}
}
When I'm navigating to the DocumentPage I call:
SimpleIoc.Default.Register<IRpcDocument , RpcDocumentA>();
await NavigationService.NavigateAsync(typeof(DocumentPage), DocumentIdParameter);
The app then navigates to the DocumentPage, constructs the RpcDocumentA, makes the necessary RPC calls to fetch the data and shows the document.
The first line tells the IoC framework it should expect an RpcDocumentA in its constructor, the second one triggers navigation. So in this case, im not registering the interface in the static ViewModelLocator().
So for each time I navigate I call SimpleIoc.Default.Register<IRpcDocument , RpcDocumentA> or SimpleIoc.Default.Register<IRpcDocument , RpcDocumentB>
This works, but is this the right way to do this? I suspect it's not.
My current set up:
Xamarin Forms, consisting of iOS, Android, WP app and shared PCL.
Using MVVM Light to keep a nice separation of concerns.
Brief intro into what I want to achieve. I want to have a Base page that has a Cancel and Next button. On pressing the Next button Content is loaded dynamically within that base page.
Xaml View:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="LogInPresenterView">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Cancel" Priority="0" Order="Primary" Command="{Binding Cancel}"></ToolbarItem>
<ToolbarItem Text="Next" Priority="1" Order="Primary" Command="{Binding Next}"></ToolbarItem>
</ContentPage.ToolbarItems>
</ContentPage>
ViewModel Code:
public class LogInPresenterViewModel : ViewModelBase
{
public LogInPresenterViewModel() {}
private RelayCommand _next;
public RelayCommand Next
{
get
{
return _next ?? (_next = new RelayCommand(async () => await DoNext()));
}
}
private async Task DoNext()
{
// IN HERE I WOULD LIKE TO DYNCAMICALLY LOAD CONTENT / VIEWS
}
}
Usually you would have a StackLayout etc before the element. However, on clicking the Next Toolbar Item I want to dynamically load content (that has a viewmodel).
So maybe my ICommand for my next button checked to see what the current content type was, and depending on that I would load another bit of content.
The scenario would be, the base page would load along with the first bit of content - Enter Email and Password. User enters that then clicks on next, if all ok, the content is replaced with the option to enter a security code, keeping the base Close and Next buttons at the top.
Hopefully this makes sense. I know what I want to do in my head, I just don't know how to translate that into Xamarin Forms...
Ok,
So first job is to create your region service in your PCL. This will look something like this:
using System;
using System.Collections.Generic;
namespace xxx
{
public class RegionService : IRegionService
{
private Dictionary<string, object> _regionDictionary;
public RegionService ()
{
_regionDictionary = new Dictionary<string, object> ();
}
#region IRegionService implementation
public bool RegisterRegion (string regionName, object regionObject)
{
object region = null;
_regionDictionary.TryGetValue (regionName, out region);
if (region != null)
_regionDictionary [regionName] = regionObject;
else
_regionDictionary.Add (regionName, regionObject);
return true;
}
public object ResolveRegion (string regionName)
{
object region = null;
_regionDictionary.TryGetValue (regionName, out region);
if (region == null)
throw new RegionServiceException ("Unable to resolve region with given name");
return region;
}
#endregion
}
}
This when you create your page with the dynamic content register your dynamic contentview in your code behind:
ContentView contentView = this.FindById<ContentView>("myContentView");
regionService.RegisterRegion("DynamicView", contentView);
You'll need to create an interface for your views and pages to use to indicate which region they wish to be presented in:
using System;
namespace xxx
{
public interface IRegionView
{
string GetRegionName ();
}
}
Then in your code behind for your view implement this interface to return the name of the region to display in.
You now need a custom presenter to use this region code. I use MVVMCross, so the details will vary for the MVVM implementation you are using, but essentially something like this is what you need:
public async static Task PresentPage(Page page)
{
if (typeof(IRegionView).GetTypeInfo().IsAssignableFrom(page.GetType().GetTypeInfo()))
{
IRegionService regionService = Mvx.Resolve<IRegionService>();
string regionName = (page as IRegionView).GetRegionName();
Page region = regionService.ResolveRegion(regionName) as Page;
if (typeof(IModalPage).GetTypeInfo().IsAssignableFrom(page.GetType().GetTypeInfo()))
await region.Navigation.PushModalAsync(page);
else if (typeof(IPopupPage).GetTypeInfo().IsAssignableFrom(page.GetType().GetTypeInfo()))
region.PushOverlayPage(page);
else if (typeof(NavigationPage).GetTypeInfo().IsAssignableFrom(region.GetType().GetTypeInfo()))
await (region as NavigationPage).PushAsync(page);
}
}
I hope this is useful for you :)
So if this was me. I would create a region service where the contentview registers a unique region name.
Content would then be marked to use that region, and a custom presenter can be used to show the view model's content in the appropriate region.
I'm on my phone whilst travelling at the moment but I can post some code later on if that helps :)
Tristan
You can dynamically load Xamarin Forms UI with XAML.
Old Answer:
This can be achieved with the use of the LoadFromXaml method. It works in the same was as XamlReader.Load in Silverlight/WPF. It is a hidden method that can be only accessed through reflection. There is an article on how to do it here:
http://www.cazzulino.com/dynamic-forms.html
But, I would like to ask to you go to this feature request at Xamarin and ask that the method be made public so that it becomes a fully supported feature:
https://forums.xamarin.com/discussion/comment/252626
I have a simple RCP application. I have a perspective and three views added to it. Initially one of the view will be disabled for the users. There is a toolbar item which launches a dialog. User authenticates himself in the dialog. After successful authentication, I want to make the view editable. I could get the reference of that specific view in my dialog.But I dont know how to enable it. I could not use selection listener as I am not selecting anything. Also I saw an example about using activities extension. But that opens/closes the view and not just enable/disable it. Can someone help me? Thanks.
As I understand you, you want to show the view in one of two states: either disabled if the user is not authenticated, or enabled when the user has been authenticated.
This is actually pretty easy :-) and I have made a small example application for you that illustrates the technique: so-edi.zip
UPDATED with new link
In RCP 3.x you have to expose the View's Control's enabled state in your implementation of ViewPart:
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.part.ViewPart;
public class View extends ViewPart {
private Control control;
#Override
public void createPartControl(Composite parent) {
control = new Composite(parent, SWT.NONE);
}
#Override
public void setFocus() {
}
public void setEnabled(boolean enabled) {
control.setEnabled(enabled);
}
public boolean isEnabled() {
return control.getEnabled()
}
}
I have implemented the RIA WCF side to authenticate with Forms Authentication and everything works from the client as expected.
This application should only allow registered users to use it (users are created by admin - no registeration page).
My question is then, what (or where) should be the efficient way to make the authentication; it has to show up at application start up (unless remember me was on and cookie is still active) and if the user logs out, it should automatically get out the interface and return to login form again.
Update (code trimmed for brevity):
Public Class MainViewModel
....
Public Property Content As Object 'DP property
Private Sub ValidateUser()
If Not IsUserValid Login()
End Sub
Private Sub Login()
'I want, that when the login returns a success it should continue
'navigating to the original content i.e.
Dim _content = Me.Content
Me.Content = Navigate(Of LoginPage)
If IsUserValid Then Me.Content = _content
End Sub
End Class
I saw you other question so I assume you're using mvvm. I accomplish this by creating a RootPage with a grid control and a navigation frame. I set the RootVisual to the RootPage. I bind the navigation frames source to a variable in the RootPageVM, then in the consructor of RootPageVM you can set the frame source to either MainPage or LoginPage based on user auth. RootPageVM can also receive messages to control further navigation like logging out.
Using MVVM-Light.
So, in the RootPageView (set as the RootVisual), something like:
public RootPageViewModel()
{
Messenger.Default.Register<NotificationMessage>
(this, "NavigationRequest", Navigate);
if (IsInDesignMode)
{
}
else
{
FrameSource =
WebContext.Current.User.IsAuthenticated ?
"Home" :
"Login";
}
}
And a method for navigation:
private void Navigate(NotificationMessage obj)
{
FrameSource = obj.Notification;
}
In the LoginViewModel:
if (loginOperation.LoginSuccess)
{
Messenger.Default.Send
(new NotificationMessage(this, "Home"), "NavigationRequest");
}