前言
曾經我以學習的目的寫了關於在.NET Core3.1使用Prism的系列文章.NET Core 3 WPF MVVM框架 Prism系列文章索引,也謝謝大家的支援,事實上當初的版本則是Prism7.2.0.1442(7.2)版本,而現在也釋出了.NET5和最新的Prism8.0.0.1909(8.0)版本,因此同樣的我想將之前的Prism Demo專案可以升級到最新,寫這篇文章的目的是自己也能學習一番,而更多的是回答那些在我Prism系列文章下面留下的我認為可以拿來一講一些問題,而有些問題我則是水平有限回答不了(真的不是不想回答)
然後我拿之前的Prism Demo專案,WPF從.NET Core3.1升級到.NET 5其實非常簡單,無腦修改專案的TargetFramework
為net5.0-windows
就行了,但是當Prism7.2升級到Prism8.0,我發現build的時候報了很多錯誤,那麼讓我們來看看究竟Prism8.0更新了些啥
一 .Prism8.0更新了什麼?
我們先來看下關於Prism7.2和Prism8.0的程式集引用情況,可推敲出一些不同:
這裡可能不會講述所有關於Prism8.0更新的全部細節,只是我認為可能主要的一些功能,我們可以看到Prism8.0相比Prism7.2,在Prism.WPF
中去除了System.Windows.Interactivity
和CommonServiceLocator
程式集,引入了Microsoft.Xaml.Behaviors.Wpf
,實際上Prism8.0做了以下整合:
- 用
Microsoft.Xaml.Behaviors.Wpf
替換System.Windows.Interactivity
CommonServiceLocator
整合入Prism.Core
之中
因為你從舊版本更新到Prism8.0可能會發生報錯,而我的目的則是一篇更新指南,關於Prism8.0更新的全部細節,可以看官方在github的Prism8.0的ReleaseNote,這裡還推薦Dior大佬的有關Prism8.0的文章:[Windows] Prism 8.0 入門(上):Prism.Core和[Windows] Prism 8.0 入門(下):Prism.Wpf 和 Prism.Unity
1.ContainerLocator.Current.Resolve函式去除:
ContainerLocator.Current.Resolve<T>
//替換為
ServiceLocator.Current.GetInstance<T>
這可能是你遇到的第一個升級報錯,因為ContainerLocator.Current.Resolve<T>
這個api本來是在Prism.WPF
下的CommonServiceLocator
程式集下面的,8.0時候被砍了,在Prism.Core
加上ServiceLocator.Current.GetInstance<T>
用於替換,切掉了CommonServiceLocator
程式集,我覺得非常合理,因為該功能本身就應該是IOC裡面的公共功能
2.有關事件轉命令的程式集變化:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
//替換為
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
這可能是你遇到的第二個升級報錯,由於用Microsoft.Xaml.Behaviors.Wpf
替換System.Windows.Interactivity
,因此,xaml的xmlns也需要對應更改
3.去除 Bootstrapper :
public partial class App : Bootstrapper
//替換成
public partial class App : PrismApplication //(推薦)其他平臺也支援
//or
public partial class App : PrismBootstrapper //WPF獨有
這可能是你遇到的第三個升級報錯,我們在App.cs中都會整合一個底層類用於註冊或者配置,其實在Prism7.2的時候Bootstrapper
已經被標記為棄用狀態,而在Prism8.0更是直接刪除,推薦繼承PrismApplication
(各平臺都支援),當然也可以選擇PrismBootstrapper
(WPF獨有)
4.IOC新增新註冊功能:
其實IOC這部分功能我不打算細講,因為其實不屬於Prism的特性功能,因為Prism預設支援兩個IOC擴充套件,也就是Unity和DryIoc的,而新新增的功能也是對應通過兩個IOC支援實現的,直接看程式碼示例:
public interface ITestService { }
public interface ITest2Service { }
public class TestService : ITestService, ITest2Service { }
private static ITestService TestDelegate() =>new TestService();
//新增支援註冊多服務對應單實現類的功能
var services = new[] { typeof(ITestService), typeof(ITest2Service) };
IContainerRegistry.RegisterManySingleton<TestService>(services);//註冊成單例模式
IContainerRegistry.RegisterMany<TestService>(services);//註冊成瞬時模式
//新增支援註冊服務為scope(範圍模式)
IContainerRegistry.RegisterScoped(typeof(TestService))//單服務
IContainerRegistry.RegisterScoped(typeof(TestService), typeof(TestService))//單服務
IContainerRegistry.RegisterScoped<TestService>();//單服務泛型版本
IContainerRegistry.RegisterScoped(typeof(ITestService), typeof(TestService))//單服務單實現
//新增支援通過委託方法註冊服務
IContainerRegistry.Register(typeof(ITestService), TestDelegate)//註冊為瞬時模式
IContainerRegistry.RegisterSingleton(typeof(ITestService), TestDelegate)//註冊為單例模式
IContainerRegistry.RegisterScoped(typeof(ITestService), TestDelegate)//註冊為範圍模式
5.新增了有關在void方法中非同步等待Task的擴充套件方法:
你乍一看好像沒什麼卵用,但是裡面還是有說法的,我們來看一個例子,WPF介面MVVM非同步讀取耗時資料載入介面,這裡是xaml的簡化程式碼::
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid Grid.Row="1" IsReadOnly="True" ItemsSource="{Binding AllMedicines}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Type" Binding="{Binding Type}"/>
<DataGridTextColumn Header="Unit" Binding="{Binding Unit}"/>
</DataGrid.Columns>
</DataGrid>
ViewModel簡化程式碼:
private ObservableCollection<Medicine> _allMedicines=new ObservableCollection<Medicine>();
public ObservableCollection<Medicine> AllMedicines
{
get { return _allMedicines; }
set { _allMedicines = value; }
}
private DelegateCommand _loadCommand;
public DelegateCommand LoadCommand =>
_loadCommand ?? (_loadCommand = new DelegateCommand(ExecuteLoadCommand));
async void ExecuteLoadCommand()
{
await ALongTask();
this.AllMedicines.AddRange(_medicineSerivce.GetAllMedicines());
}
private async Task ALongTask()
{
await Task.Delay(3000);//模擬耗時操作
Debug.WriteLine("耗時操作完成");
}
這是正常我們會實現的方式,同樣的也確實不會出現跨執行緒問題(在非UI執行緒操作ObservableCollection
集合會出現),關於async await在WPF不會出現跨執行緒問題,可以參考我的另外一篇文章非同步函式async await在wpf都做了什麼?,也同樣的在執行耗時操作時候不會阻塞UI主執行緒,如果在最上層不用async void能否實現同樣的效果,這就是TaskExtension
的意義了,下面只例舉非泛型版本TaskExtension
的api,,實際還有泛型版本的TaskExtension
,我們拿最多引數的過載方法來說明:
public static class TaskExtensions
{
public static async void Await(this Task task, Action completedCallback, Action<Exception> errorCallback, bool configureAwait)
{
try
{
await task.ConfigureAwait(configureAwait);
completedCallback?.Invoke();
}
catch (Exception obj)
{
errorCallback?.Invoke(obj);
}
}
}
1.completedCallback:當前Task的回撥函式,指Task執行的後續操作
2.errorCallback:回撥函式的異常回撥函式,回撥函式異常後可以執行
3.configureAwait:指示回撥函式是否在當前執行上下文執行,True為是,false為否
我們可以把ExecuteLoadCommand方法修改下:
void ExecuteLoadCommand()
{
//TaskExtension for async void Command
ALongTask().Await( completedCallback:() =>
{
this.AllMedicines.AddRange(_medicineSerivce.GetAllMedicines());
}, errorCallback:null,configureAwait:true);
}
該方式執行效果和之前一樣,而且不用在void方法加上async 和方法內部await就能實現非同步等待操作,而這只是推薦在Command的Excuted Method使用,這也是官方推薦的,因為一般Excuted Method返回值只會是void
二.回答一些問題
如何在Prism使用AOP?
其實AOP並不是屬於prism特有的功能,但是由於prism支援擴充套件IOC容器:Unity和DryIoc,只要其IOC容器本身支援,那就可以,由於預設Prism是以Unity為預設IOC容器,所以以Unity為例子:
-
NuGet引用Unity AOP庫:Unity.Interception(最新是5.11.1)
-
在App.cs新增擴充套件AOP,程式碼如下:
protected override void RegisterTypes(IContainerRegistry containerRegistry) { var container = PrismIocExtensions.GetContainer(containerRegistry); container.AddNewExtension<Interception>()//add Extension Aop //註冊服務和新增顯示攔截 .RegisterType<IMedicineSerivce, MedicineSerivce>(new Interceptor<InterfaceInterceptor>(), new InterceptionBehavior<PolicyInjectionBehavior>()) .RegisterType<IPatientService, PatientService>(new Interceptor<InterfaceInterceptor>(), new InterceptionBehavior<PolicyInjectionBehavior>()) .RegisterType<IUserService, UserService>(new Interceptor<InterfaceInterceptor>(), new InterceptionBehavior<PolicyInjectionBehavior>()); }
-
新建類
LogHandler
繼承ICallHandler
用於處理攔截邏輯和特性LogHandlerAttribute
,模擬記錄Log,:public class LogHandler : ICallHandler { public int Order { get ; set ; } public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { Debug.WriteLine("-------------Method Excute Befored-------------"); Debug.WriteLine($"Method Name:{input.MethodBase.Name}"); if (input.Arguments.Count>0) { Debug.WriteLine("Arguments:"); for (int i = 0; i < input.Arguments.Count; i++) { Debug.WriteLine($"parameterName:{input.Arguments.ParameterName(i)},parameterValue:{input.Arguments[i]}"); } } var methodReturn = getNext()(input, getNext); Debug.WriteLine("-------------Method Excute After-------------"); if (methodReturn.Exception!=null) { Debug.WriteLine($"Exception:{methodReturn.Exception.Message} \n"); } else { Debug.WriteLine($"Excuted Successed \n"); } return methodReturn; } } public class LogHandlerAttribute : HandlerAttribute { public override ICallHandler CreateHandler(IUnityContainer container) { return new LogHandler() { Order = this.Order }; } }
-
為那些需要攔截的介面標上Attribute
[LogHandler] public interface IMedicineSerivce { List<Medicine> GetAllMedicines(); List<Recipe> GetRecipesByPatientId(int patientId); } [LogHandler] public interface IPatientService { List<Patient> GetAllPatients(); } [LogHandler] public interface IUserService { List<User> GetAllUsers(); }
效果如下:
Vs輸出:
-------------Method Excute Befored-------------
Method Name:GetAllMedicines
-------------Method Excute After-------------
Excuted Successed
-------------Method Excute Befored-------------
Method Name:GetRecipesByPatientId
Arguments:
parameterName:patientId,parameterValue:1
-------------Method Excute After-------------
Excuted Successed
-------------Method Excute Befored-------------
Method Name:GetRecipesByPatientId
Arguments:
parameterName:patientId,parameterValue:2
-------------Method Excute After-------------
Excuted Successed
-------------Method Excute Befored-------------
Method Name:GetRecipesByPatientId
Arguments:
parameterName:patientId,parameterValue:3
-------------Method Excute After-------------
Excuted Successed
-------------Method Excute Befored-------------
Method Name:GetRecipesByPatientId
Arguments:
parameterName:patientId,parameterValue:4
-------------Method Excute After-------------
Excuted Successed
當然這裡篇幅有限,不可能講述有關太多Unity AOP的細節,實際上Unity AOP功能非常強大,同樣支援通過配置檔案來配置AOP和支援對不同型別方法的攔截,需要了解更多細節在這裡可推薦該博文C#中AOP_使用Unity實現AOP
是否所有事件和邏輯都在ViewModel處理?
WPF是個資料驅動型程式,當使用MVVM框架如Prism或者MVVMLight的時候,我們會在ViewModel處理業務資料邏輯,通過Binding方式驅動前臺介面的顯示,如果處理邏輯是View相關的,例如對控制元件的樣式變化,滑鼠移動控制元件等View邏輯相關的,這時候則推薦用依賴或者附加屬性,或在View的Code-behind的cs檔案中事件來處理有關View的邏輯,不要為了所謂的MVVM而把一切邏輯都放在ViewModel處理,實則更加不靈活,反而跟之前的MVC都放在C中處理沒啥區別了
其他問題?(待補充)
三.原始碼
四.參考
https://github.com/PrismLibrary/Prism
https://github.com/PrismLibrary/Prism/releases
C#中AOP_使用Unity實現AOP