注意
一些信息与预发布产品相关,在商业发行之前可能发生实质性修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。
本文演示了创建一个简单源提供程序的过程,该提供程序用于注册源内容 URI 并实现 IFeedProvider 接口。 小组件板会调用此接口方法来请求自定义查询字符串参数,通常是为了支持身份验证场景。 源提供程序可以支持单个源或多个源。
要使用 C++/WinRT 实现源提供程序,请参阅在 win32 应用中实现源提供程序 (C++/WinRT)。
先决条件
- 设备必须启用开发人员模式。 有关详细信息,请参阅启用用于开发的设备。
- 具有通用 Windows 平台开发工作负载的 Visual Studio 2022 或更高版本。
创建新的 C# 控制台应用
在 Visual Studio 中,创建新的项目。 在“创建新项目”对话框中,将语言筛选器设置为“C#”,将平台筛选器设置为 Windows,然后选择“控制台应用”项目模板。 将新项目命名为“ExampleFeedProvider”。 在此演示中,请确保未选中“将解决方案和项目放在同一目录中”。 出现提示时,将目标 .NET 版本设置为 6.0。
当项目加载时,在“解决方案资源管理器”中,右键单击项目名称,然后选择“属性”。 在“常规”页上,向下滚动到“目标 OS”并选择“Windows”。 在“目标操作系统版本”下,选择版本 10.022631.2787 或更高版本。
请注意,此演示使用的控制台应用在激活源时会显示控制台窗口,以便进行调试。 准备好发布源提供程序应用后,可以按照将控制台应用程序转换为 Windows 应用程序中的步骤将控制台应用转换为 Windows 应用。
添加对 Windows 应用 SDK NuGet 包的引用
此示例使用最新稳定版 Windows 应用 SDK NuGet 包。 在“解决方案资源管理器”中,右键单击“依赖项”,然后选择“管理 NuGet 包...”。在 NuGet 包管理器中,选择“浏览”选项卡并搜索“Microsoft.WindowsAppSDK”。 在“版本”下拉列表中选择最新稳定版本,然后单击“安装”。
添加一个 FeedProvider 类来处理源操作
在 Visual Studio 中,右键单击“解决方案资源管理器”中的 ExampleFeedProvider
项目并选择“添加”-“类”。 在“添加类”对话框中,将类命名为“FeedProvider”,然后单击“添加”。 在生成的 FeedProvider.cs 文件中,更新类定义以指示它实现 IFeedProvider 接口。
创建一个 CLSID,以用于标识激活 COM 的源提供程序。 转到“工具”->“创建 GUID”,在 Visual Studio 中生成 GUID。 将此 GUID 保存到一个文本文件中,以便稍后在打包源提供程序应用时使用。 替换以下示例中所示 FeedProvider 类的注释中的 GUID。
// FeedProvider.cs
using Microsoft.Windows.Widgets.Feeds.Providers;
...
[ComVisible(true)]
[ComDefaultInterface(typeof(IFeedProvider))]
[Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]
public sealed class FeedProvider : IFeedProvider
实现 IFeedProvider 方法
在接下来的几个部分中,我们将实现 IFeedProvider 接口的方法。
注意
传递到 IFeedProvider 接口回调方法的对象仅保证在回调中有效。 不应存储对这些对象的引用,因为它们在回调上下文之外的行为未定义。
启用Feed提供者
当小组件板主机创建与提供程序关联的源时,将调用 OnFeedProviderEnabled 方法。 在此方法的实现中,使用将传递给提供源内容的 URL 的参数生成一个查询字符串,包括任何必要的身份验证令牌。 创建一个 CustomQueryParametersUpdateOptions 实例,从标识已启用的源和查询字符串的事件参数传入 FeedProviderDefinitionId。 获取默认的 FeedManager 并调用 SetCustomQueryParameters,从而将查询字符串参数注册到小组件板。
// FeedProvider.cs
public void OnFeedProviderEnabled(FeedProviderEnabledArgs args)
{
Console.WriteLine($"{args.FeedProviderDefinitionId} feed provider was enabled.");
var updateOptions = new CustomQueryParametersUpdateOptions(args.FeedProviderDefinitionId, "param1¶m2");
FeedManager.GetDefault().SetCustomQueryParameters(updateOptions);
}
供给提供者被禁用时
此提供程序的所有源都已禁用时,小组件板将调用 OnFeedProviderDisabled。 源提供程序无需执行任何操作即可响应此方法调用。 方法调用可用于遥测目的,或根据需要更新查询字符串参数或撤销身份验证令牌。 如果应用仅支持单个源提供程序,或者应用支持的所有源提供程序都已被禁用,则应用可以退出以响应此回调。
// FeedProvider.cs
public void OnFeedProviderDisabled(FeedProviderDisabledArgs args)
{
Console.WriteLine($"{args.FeedProviderDefinitionId} feed provider was disabled.");
}
OnFeedEnabled、OnFeedDisabled
启用或禁用源时,小组件板将调用 OnFeedEnabled 和 OnFeedDisabled。 源提供程序无需执行任何操作即可响应这些方法调用。 方法调用可用于遥测目的,或根据需要更新查询字符串参数或撤销身份验证令牌。
// FeedProvider.cs
public void OnFeedEnabled(FeedEnabledArgs args)
{
Console.WriteLine($"{args.FeedDefinitionId} feed was enabled.");
}
// FeedProvider.cs
public void OnFeedDisabled(FeedDisabledArgs args)
{
Console.WriteLine($"{args.FeedDefinitionId} feed was disabled.");
}
OnCustomQueryParametersRequested
当小组件板确定需要刷新与源提供程序关联的自定义查询参数时,将引发 OnCustomQueryParametersRequested。 例如,从远程 Web 服务提取源内容的操作失败时,可能会调用此方法。 传递到此方法的 CustomQueryParametersRequestedArgs 的 FeedProviderDefinitionId 属性指定了要为其请求查询字符串参数的源。 提供程序应重新生成查询字符串,并通过调用 SetCustomQueryParameters 将其传回小组件板。
// FeedProvider.cs
public void OnCustomQueryParametersRequested(CustomQueryParametersRequestedArgs args)
{
Console.WriteLine($"CustomQueryParamaters were requested for {args.FeedProviderDefinitionId}.");
var updateOptions = new CustomQueryParametersUpdateOptions(args.FeedProviderDefinitionId, "param1¶m2");
FeedManager.GetDefault().SetCustomQueryParameters(updateOptions);
}
实现将按请求实例化 FeedProvider 的类工厂
要使源主机与源提供程序进行通信,必须调用 CoRegisterClassObject。 此函数要求我们创建 IClassFactory 的实现,以便为 FeedProvider 类创建类对象。 我们将在自包含帮助程序类中实现类工厂。
在 Visual Studio 中,右键单击“解决方案资源管理器”中的 ExampleFeedProvider
项目并选择“添加”-“类”。 在“添加类”对话框中,将类命名为 FactoryHelper,然后单击“添加”。
将 FactoryHelper.cs 文件的内容替换为以下代码。 此代码定义了 IClassFactory 接口,并实现它的两种方法:CreateInstance 和 LockServer。 此代码是用于实现类工厂的典型样本,并不特定于源提供程序的功能,除非我们指示正在创建的类对象实现 IFeedProvider 接口。
// FactoryHelper.cs
using Microsoft.Windows.Widgets.Feeds.Providers;
using System.Runtime.InteropServices;
using WinRT;
namespace ExampleFeedProvider
{
namespace Com
{
static class Guids
{
public const string IClassFactory = "00000001-0000-0000-C000-000000000046";
public const string IUnknown = "00000000-0000-0000-C000-000000000046";
}
[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(Guids.IClassFactory)]
internal interface IClassFactory
{
[PreserveSig]
int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
[PreserveSig]
int LockServer(bool fLock);
}
static class ClassObject
{
public static void Register(Guid clsid, object pUnk, out uint cookie)
{
[DllImport("ole32.dll")]
static extern int CoRegisterClassObject(
[MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnk,
uint dwClsContext,
uint flags,
out uint lpdwRegister);
int result = CoRegisterClassObject(clsid, pUnk, 0x4, 0x1, out cookie);
if (result != 0)
{
Marshal.ThrowExceptionForHR(result);
}
}
public static int Revoke(uint cookie)
{
[DllImport("ole32.dll")]
static extern int CoRevokeClassObject(uint dwRegister);
return CoRevokeClassObject(cookie);
}
}
}
internal class FeedProviderFactory<T> : Com.IClassFactory
where T : IFeedProvider, new()
{
public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
{
ppvObject = IntPtr.Zero;
if (pUnkOuter != IntPtr.Zero)
{
Marshal.ThrowExceptionForHR(CLASS_E_NOAGGREGATION);
}
if (riid == typeof(T).GUID || riid == Guid.Parse(Com.Guids.IUnknown))
{
// Create the instance of the .NET object
ppvObject = MarshalInspectable<IFeedProvider>.FromManaged(new T());
}
else
{
// The object that ppvObject points to does not support the
// interface identified by riid.
Marshal.ThrowExceptionForHR(E_NOINTERFACE);
}
return 0;
}
int Com.IClassFactory.LockServer(bool fLock)
{
return 0;
}
private const int CLASS_E_NOAGGREGATION = -2147221232;
private const int E_NOINTERFACE = -2147467262;
}
}
将源提供程序类对象注册到 OLE
在可执行文件的 Program.cs 文件中,我们将调用 CoRegisterClassObject,以将源提供程序注册到 OLE,从而让小组件板可以与其交互。 将 Program.cs 的内容替换为以下代码。 这将使用 我们在上一步中定义的 FeedProviderFactory 接口来注册 FeedProvider 帮助程序类。 为方便调试,此示例在默认的 FeedManager 实例上调用 GetEnabledFeedProviders,以获取表示已启用源提供程序的 FeedProviderInfo 对象列表。 然后它会循环访问已启用的源提供程序,使用 EnabledFeedDefinitionIds 属性来列出所有已启用源的 ID。
// Program.cs
using Microsoft.Windows.Widgets.Feeds.Providers;
using Microsoft.Windows.Widgets.Providers;
using System;
using System.Runtime.InteropServices;
namespace ExampleFeedProvider
{
public static class Program
{
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
[MTAThread]
static void Main(string[] args)
{
Console.WriteLine("FeedProvider Starting...");
if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
{
WinRT.ComWrappersSupport.InitializeComWrappers();
uint registrationHandle;
var factory = new FeedProviderFactory<FeedProvider>();
Com.ClassObject.Register(typeof(FeedProvider).GUID, factory, out registrationHandle);
Console.WriteLine("Feed Provider registered.");
var existingFeedProviders = FeedManager.GetDefault().GetEnabledFeedProviders();
if (existingFeedProviders != null)
{
Console.WriteLine($"There are {existingFeedProviders.Length} FeedProviders currently outstanding:");
foreach (var feedProvider in existingFeedProviders)
{
Console.WriteLine($" ProviderId: {feedProvider.FeedProviderDefinitionId}, DefinitionIds: ");
var m = WidgetManager.GetDefault().GetWidgetIds();
if (feedProvider.EnabledFeedDefinitionIds != null)
{
foreach (var enabledFeedId in feedProvider.EnabledFeedDefinitionIds)
{
Console.WriteLine($" {enabledFeedId} ");
}
}
}
}
if (GetConsoleWindow() != IntPtr.Zero)
{
Console.WriteLine("Press ENTER to exit.");
Console.ReadLine();
}
else
{
while (true)
{
// You should fire an event when all the outstanding
// FeedProviders have been disabled and exit the app.
}
}
}
else
{
Console.WriteLine("Not being launched to service Feed Provider... exiting.");
}
}
}
}
请注意,此代码示例导入 GetConsoleWindow 函数以确定应用是否作为控制台应用程序运行,这是本演练的默认行为。 如果函数返回的指针有效,我们会将调试信息写入控制台。 否则,应用将作为 Windows 应用运行。 在这种情况下,如果已启用源提供程序列表为空,当退出应用时,我们将等待在 OnFeedProviderDisabled 方法中设置的事件。 有关将示例控制台应用转换为 Windows 应用的信息,请参阅将控制台应用转换为 Windows 应用。
打包源提供程序应用
在当前发行版中,只有打包的应用才能注册为源提供程序。 以下步骤将详细演示打包应用并更新应用部件清单,以将应用作为源提供程序注册到操作系统的过程。
创建 MSIX 打包项目
在“解决方案资源管理器”中,右键单击所需解决方案,然后选择“添加”-“新项目...”。在“添加新项目”对话框中,选择“Windows 应用程序打包项目”模板,然后单击“下一步”。 将项目名称设置为“ExampleFeedProviderPackage”,然后单击“创建”。 出现提示时,将目标版本设置为版本 22621 或更高版本,然后单击“确定”。 然后右键单击“ExampleFeedProviderPackage”项目,再选择“添加”->“项目引用”。 选择 ExampleFeedProvider 项目,然后单击“确定”。
将 Windows 应用 SDK 包引用添加到打包项目
需要将对 Windows 应用 SDK nuget 包的引用添加到 MSIX 打包项目。 在“解决方案资源管理器”中,双击 ExampleFeedProviderPackage 项目以打开 ExampleFeedProviderPackage.wapproj 文件。 在 Project 元素中添加以下 xml。
<!--ExampleWidgetProviderPackage.wapproj-->
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.231116003-experimentalpr">
<IncludeAssets>build</IncludeAssets>
</PackageReference>
</ItemGroup>
注意
确保 PackageReference 元素中指定的版本与在上一步中引用的最新稳定版本匹配。
如果计算机上已安装正确版本的 Windows 应用 SDK,并且你不希望在包中捆绑 SDK 运行时,则可以在 ExampleFeedProviderPackage 项目的 Package.appxmanifest 文件中指定包依赖项。
<!--Package.appxmanifest-->
...
<Dependencies>
...
<PackageDependency Name="Microsoft.WindowsAppRuntime.1.5.233430000-experimental1" MinVersion="2000.638.7.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
...
</Dependencies>
...
更新包清单
在“解决方案资源管理器”中,右键单击 文件并选择“查看代码”以打开清单 xml 文件。 接下来,需要为我们将使用的应用包扩展添加一些命名空间声明。 将以下命名空间定义添加到顶级 Package 元素。
<!-- Package.appmanifest -->
<Package
...
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
在 Application 元素内,创建名为 Extensions 的新空元素。 请确保此新元素位于 uap:VisualElements 的结束标记之后。
<!-- Package.appxmanifest -->
<Application>
...
<Extensions>
</Extensions>
</Application>
需要添加的第一个扩展是 ComServer 扩展。 这会向 OS 注册可执行文件的入口点。 此扩展是打包的应用,等效于通过设置注册表项注册 COM 服务器,它并不特定于小组件提供程序。添加以下 com:Extension 元素作为 Extension 元素的子元素。 定义 FeedProvider 类时,请在 com:Class 元素的 Id 属性中将 GUID 更改为在上一步中生成的 GUID。
<!-- Package.appxmanifest -->
<Extensions>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="ExampleFeedProvider\ExampleFeedProvider.exe" Arguments="-RegisterProcessAsComServer" DisplayName="C# Feed Provider App">
<com:Class Id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" DisplayName="FeedProvider" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
</Extensions>
然后添加将应用注册为源提供程序的扩展。 将 uap3:Extension 元素粘贴到以下代码片段中,作为 Extension 元素的子元素。 请务必将 COM 元素的 ClassId 属性替换为前面步骤中使用的 GUID。
<!-- Package.appxmanifest -->
<Extensions>
...
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.windows.widgets.feeds" DisplayName="ContosoFeed" Id="com.examplewidgets.examplefeed" PublicFolder="Public">
<uap3:Properties>
<FeedProvider Icon="ms-appx:Assets\StoreLogo.png" Description="FeedDescription">
<Activation>
<!-- Apps exports COM interface which implements IFeedProvider -->
<CreateInstance ClassId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
</Activation>
<Definitions>
<Definition Id="Contoso_Feed"
DisplayName="Contoso_Feed Feed"
Description="Feed representing Contoso"
ContentUri="https://www.contoso.com/"
Icon="ms-appx:Images\StoreLogo.png">
</Definition>
<Definition Id="Fabrikam_Feed"
DisplayName="Fabrikam Feed"
Description="Feed representing Example"
ContentUri="https://www.fabrikam.com/"
Icon="ms-appx:Images\StoreLogo.png">
</Definition>
</Definitions>
</FeedProvider>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
</Extensions>
有关所有这些元素的详细说明和格式信息,请参阅源提供程序包清单 XML 格式。
测试源提供程序
确保已从“解决方案平台”下拉列表中选择与开发计算机匹配的体系结构,例如“x64”。 在“解决方案资源管理器”中,右键单击所需解决方案,然后选择“生成解决方案”。 完成此操作后,右键单击 ExampleWidgetProviderPackage 并选择“部署”。 控制台应用应会在部署时启动,你将在控制台输出中看到已启用源。 打开小组件板,在“源”部分的顶部选项卡中应会看到新的源。
调试源提供程序
固定源后,小组件平台将启动源提供程序应用程序,以便接收和发送有关该源的相关信息。 要调试正在运行的源,可以将调试程序附加到正在运行的源提供程序应用程序,也可以将 Visual Studio 设置为在启动源提供程序进程后自动开始调试源提供程序进程。
要附加到正在运行的进程,请执行以下操作:
- 在 Visual Studio 中,单击“调试”->“附加到进程”。
- 筛选进程并找到所需是源提供程序应用程序。
- 附加调试程序。
要在小组件最初启动时将调试程序自动附加到进程,请执行以下操作:
- 在 Visual Studio 中,选择“调试”->“其他调试目标”->“调试安装的应用包”>。
- 筛选包并查找所需的源提供程序包。
- 选择它并选中显示“不启动,但在启动时调试代码”的框。
- 单击 “附加” 。
将控制台应用转换为 Windows 应用
要将本演练中创建的控制台应用转换为 Windows 应用,请在“解决方案资源管理器”中右键单击 ExampleFeedProvider 项目,然后选择“属性”。 在“应用程序”->“常规”下,将“输出类型”从“控制台应用程序”更改为“Windows 应用程序”。
发布源提供程序应用
开发和测试源提供程序后,可以在 Microsoft Store 上发布应用,以便用户在其设备上安装源。 有关发布应用的分步指南,请参阅在 Microsoft Store 中发布应用。
源存储集合
在 Microsoft 应用商店上发布应用后,可以请求将应用包含在源应用商店集合中,以帮助用户发现具有 Windows 源的应用。 若要提交请求,请参阅 “提交源/板”以获取应用商店集合的附加内容。