通过


在 C# Windows 应用中实现源供给器

注意

一些信息与预发布产品相关,在商业发行之前可能发生实质性修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

本文为您演示如何创建一个简单的订阅源提供程序,该提供程序用于注册订阅源内容 URI,并实现 IFeedProvider 接口。 小组件板会调用此接口方法来请求自定义查询字符串参数,通常是为了支持身份验证场景。 数据源提供者可以支持单个数据源或多个数据源。

要使用 C++/WinRT 实现源提供程序,请参阅在 win32 应用中实现源提供程序 (C++/WinRT)

先决条件

  • 设备必须启用开发人员模式。 有关详细信息,请参阅 开发人员的设置
  • 使用 WinUI 应用程序开发 工作负载的 Visual Studio 2026 或更高版本。

创建新的 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 接口。

创建一个用于 COM 激活时标识您的数据提供程序的 CLSID。 在 Visual Studio 中,通过转到工具->创建 GUID来生成 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&param2");
    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 服务提取源内容的操作失败时,可能会调用此方法。 传入此方法的 CustomQueryParametersRequestedArgsFeedProviderDefinitionId 属性指定了请求查询字符串参数的源。 提供程序应重新生成查询字符串,并通过调用 SetCustomQueryParameters 将其传回小组件板。

// FeedProvider.cs

public void OnCustomQueryParametersRequested(CustomQueryParametersRequestedArgs args)
{
    Console.WriteLine($"CustomQueryParamaters were requested for {args.FeedProviderDefinitionId}.");
    var updateOptions = new CustomQueryParametersUpdateOptions(args.FeedProviderDefinitionId, "param1&param2");
    FeedManager.GetDefault().SetCustomQueryParameters(updateOptions);
}

实现将按请求实例化 FeedProvider 的类工厂

为了使馈送主机与馈送提供程序进行通信,必须调用 CoRegisterClassObject。 此函数要求我们创建 IClassFactory 的实现,以便为 FeedProvider 类创建类对象。 我们将在自包含帮助程序类中实现类工厂。

在 Visual Studio 中,右键单击“解决方案资源管理器”中的 ExampleFeedProvider 项目并选择“添加”-“类”。 在“添加类”对话框中,将类命名为 FactoryHelper,然后单击“添加”。

将 FactoryHelper.cs 文件的内容替换为以下代码。 此代码定义了 IClassFactory 接口,并实现它的两种方法:CreateInstanceLockServer。 此代码是用于实现类工厂的典型样本,并不特定于源提供程序的功能,除非我们指示正在创建的类对象实现 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,以便 Widgets Board 可以与其交互。 将 Program.cs 的内容替换为以下代码。 这将使用 我们在上一步中定义的 FeedProviderFactory 接口来注册 FeedProvider 帮助程序类。 为方便调试,此示例在默认的 FeedManager 实例上调用 GetEnabledFeedProviders,以获取表示已启用源提供程序的 FeedProviderInfo 对象列表。 它遍历已启用的 Feed 提供程序,使用 EnabledFeedDefinitionIds 属性来列出所有已启用 Feed 的 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 服务器,它并不限于窗口小部件提供商。在 Extensions 元素中添加如下的 com: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 设置为在启动源提供程序进程后自动开始调试源提供程序进程。

要附加到正在运行的进程,请执行以下操作:

  1. 在 Visual Studio 中,单击“调试”->“附加到进程”。
  2. 找到并筛选进程以定位所需的信息提供程序应用程序。
  3. 连接调试器。

要在进程初始启动时自动将调试程序附加到该进程,请执行以下操作:

  1. 在 Visual Studio 中,选择“调试”->“其他调试目标”->“调试安装的应用包”>。
  2. 筛选软件包并查找所需的馈源提供程序包。
  3. 选择它并选中“不要启动,但在启动时调试我的代码”的框。
  4. 单击 “附加”

将控制台应用转换为 Windows 应用

要将本演练中创建的控制台应用转换为 Windows 应用,请在“解决方案资源管理器”中右键单击 ExampleFeedProvider 项目,然后选择“属性”。 在“应用程序”->“常规”下,将“输出类型”从“控制台应用程序”更改为“Windows 应用程序”。

显示输出类型设置为 Windows 应用程序的 C# 源提供程序项目属性的屏幕截图

发布您的源提供程序应用程序

开发和测试您的信息源提供程序后,您可以在 Microsoft Store 上发布您的应用,以便用户在他们的设备上安装信息源。 有关发布应用的分步指南,请参阅在 Microsoft Store 中发布应用

信息流存储集合

在 Microsoft 应用商店上发布应用后,可以请求将应用包含在源应用商店集合中,以帮助用户发现具有 Windows 源的应用。 若要提交请求,请参阅 “将源/板提交以添加到应用商店集合中”