热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:跨平台应用集成(在ASP.NETCoreMVC应用程序中集成MicrosoftGraph)

篇首语:本文由编程笔记#小编为大家整理,主要介绍了跨平台应用集成(在ASP.NETCoreMVC应用程序中集成MicrosoftGraph)相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了跨平台应用集成(在ASP.NET Core MVC 应用程序中集成 Microsoft Graph)相关的知识,希望对你有一定的参考价值。








1





谈一谈.NET 的跨平台



终于要写到这一篇了。跨平台的支持可以说是 Office 365 平台在设计伊始就考虑的目标。我在前面的文章已经提到过了,Microsoft Graph 服务针对一些主流的开源平台(主要用来做跨平台应用)都有支持,例如 python,nodejs 等。他们真的非常好用,与此同时我虽然对他们也有一定的了解,但要跟我最熟悉的 Microsoft .NET 来比较的话,我自然还是更喜欢后者了。


所以,一直在等待合适的时间,要来写 Microsoft .NET 的跨平台应用,这是多么令人期待的事情啊。经过一段时间的研究,我今天正式隆重地给大家介绍,如何在 ASP.NET Core 平台上面构建一个 MVC 应用程序,并且在里面集成 Microsoft Graph。


关于Microsoft .NET 这几年的发展,我是感到比较兴奋的,作为一个从.NET 1.1就开始追随的骨灰级粉丝,我很高兴地看到现在.NET 已经真正迈出了跨平台的脚步,而且完全开源了。如果要讲这个话题,恐怕我是一时半会刹不住车的,所以我就此打住吧,有兴趣的朋友们可以通过下面这个网址了解更多.NET 的发展情况。 


这一篇文章用到的技术,是最新的.NET Core 中 ASP.NET Core 提供的,我使用了其中的 MVC 这个模板创建了一个简单的应用程序,并且略微改造了一下,使其能够采用 Azure AD 进行身份验证,继而通过获得的用户凭据能实现对 Microsoft Graph 的使用。


跨平台应用集成(在ASP.NET Core MVC 应用程序中集成 Microsoft Graph)









2





ASP.NET Core MVC 整合了 Graph 的场景效果



在准备这个范例,以及编写这个文章的时候,为了全面地测试在跨平台开发方面的能力,我完全采用了一台全新的 MacBook 作为工作用机,开发工具我使用的是 Visual Studio Code(这个直接就可以在 Mac 里面运行),就像你看到的这样。


跨平台应用集成(在ASP.NET Core MVC 应用程序中集成 Microsoft Graph)


好的,大致背景我也交代清楚了,如果大家下载了代码,可以跟我一起来体验一下这个应用程序运行起来的效果吧 —— 我推荐你也用 Visual Studio Code 来打开这个应用程序。(是的,你不再需要安装 Visual Studio 完整版)


打开命令行工具,我们很快要运行几个命令 

跨平台应用集成(在ASP.NET Core MVC 应用程序中集成 Microsoft Graph)


请运行下面的命令,下载当前项目所依赖的一些组件包




dotnet restore



然后运行下面的命令,可以将当前项目运行起来




dotnet run



如果不出意外的话,我写好的这个简单的应用程序会启动起来,并且在本机的5000端口进行监听


跨平台应用集成(在ASP.NET Core MVC 应用程序中集成 Microsoft Graph)


看起来跟我们一般的 MVC 程序真的是一样一样的,此时,请点击页面顶部左上角的“About”,看看会发生什么呢


跨平台应用集成(在ASP.NET Core MVC 应用程序中集成 Microsoft Graph)


输入正确的用户名和密码后,你就可以看到该用户的基本信息了



好了,功能确实就是这样,足够简单,不是吗?但是正如我猜想你应该会想到的那样,只要打开了 Graph 这扇大门,无穷的宝藏就等着你尽情地创造性地利用了。








3





代码解析



下面我还是简单地讲解一下我在标准的模板基础上做过哪些定制,从而实现了上面的功能的。




要创建这个应用程序,你需要安装 dotnet sdk(https://www.microsoft.com/net/download/core ),然后在本地命令行工具中运行 dotnet new mvc 即可



首先,我为项目添加了几个外部组件包,这是通过修改项目定义文件(aspnetcoremvc.csproj)来实现的。










这些组件都是托管在 nuget.org 这个网站上面,甚至整个 dotnet core 的核心组件也都是开源托管在这个上面。一般添加完这些组件后,都需要运行 dotnet restore 命令在本地进行还原。



然后,我修改了 Startup.cs 文件。这是 asp.net core 应用程序的一个标准文件,用来定义程序入口,加载相关服务和中间件。(这里涉及的知识面太多,以至于我无法一一说明,有兴趣可以参考 https://asp.net/core了解。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
//这里增加了一写命名空间导入
using Microsoft.Extensions.Caching;
using Microsoft.AspNetCore.Authentication.COOKIEs;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Session;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
namespace aspntecoremvc
{
   public class Startup
   {
       // This method gets called by the runtime. Use this method to add services to the container.
       //这里引入一些服务,注入一些组件
       public void ConfigureServices(IServiceCollection services)
       {
           services.AddSession();
           services.AddAuthentication(sharedoptiOns=> sharedoptions.SignInScheme = COOKIEAuthenticationDefaults.AuthenticationScheme);
           // Add framework services.
           services.AddMvc();
       }
       //这里定义了一些静态信息
       private readonly string ClientId="e91ef175-e38d-4feb-b1ed-f243a6a81b93";
       private readonly string Authority=String.Format("https://login.microsoftonline.com/{0}","office365devlabs.onmicrosoft.com");
       private readonly string ClientSecret="2F5jdoGGNn59oxeDLE9fXx5tD86uvzIji74dmLaj3YI=";
       private readonly string GraphResourceId="https://graph.microsoft.com";
       private readonly string CallbackPath ="/signin-oidc";
       // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
       public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
       {
           loggerFactory.AddConsole();
           loggerFactory.AddDebug();
           if (env.IsDevelopment())
           {
               app.UseDeveloperExceptionPage();
               app.UseBrowserLink();
           }
           else
           {
               app.UseExceptionHandler("/Home/Error");
           }
           app.UseStaticFiles();
           //这里几步是最关键的,定义了如何进行身份认证以及如何保存
           app.UseSession();
           app.UseCOOKIEAuthentication();
           app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions{
               ClientId = ClientId,
               Authority = Authority,
               ClientSecret = ClientSecret,
               RespOnseType= OpenIdConnectResponseType.CodeIdToken,
               CallbackPath = CallbackPath,
               GetClaimsFromUserInfoEndpoint =true,
               Events = new OpenIdConnectEvents{
                   OnAuthorizationCodeReceived= OnAuthorizationCodeReceived
               }
           });
           app.UseMvc(routes =>
           {
               routes.MapRoute(
                   name: "default",
                   template: "{cOntroller=Home}/{action=Index}/{id?}");
           });
       }
       private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
       {
           // 将 Token 信息保存在 session 里面,后续 Graph 就可以直接调用了
           string userObjectId = (context.Ticket.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
           ClientCredential clientCred = new ClientCredential(ClientId, ClientSecret);
           AuthenticationContext authCOntext= new AuthenticationContext(Authority, new SampleSessionCache(userObjectId, context.HttpContext.Session));
           AuthenticationResult authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
               context.ProtocolMessage.Code, new Uri(context.Properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey]), clientCred, GraphResourceId);
       }
       private Task OnAuthenticationFailed(FailureContext context)
       {
           context.HandleResponse();
           context.Response.Redirect("/Home/Error?message=" + context.Failure.Message);
           return Task.FromResult(0);
       }
   }
}


我还专门定义了一个简单的类用来保存 Token 信息。为了简单起见,我们将 Token 保存在 Session 里面,这样的话,用户登陆一次后,在一个会话里面就不需要多次登录,而是可以直接重用这些 Token。


这个类其实我是重用了之前在 ASP.NET MVC 开发中的那个类,没有什么特别要交待的,请直接打开 SampleSessionCache.cs 这个文件了解即可。


接下来,为了便于后续在 Controller 里面快速地访问到 Graph,我对 GraphServiceClient 进行了封装,请参考 SDKHelper.cs 这个文件


using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Graph;
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Http;
namespace aspntecoremvc{
   public static class SDKHelper{
       //这里其实是对 ControllerBase 这个类型进行了扩展
       public static async Task GetAuthenticatedClient(this ControllerBase controller){
           var Authority = String.Format("https://login.microsoftonline.com/{0}","office365devlabs.onmicrosoft.com");
           var ClientId = "e91ef175-e38d-4feb-b1ed-f243a6a81b93";
           var ClientSecret = "2F5jdoGGNn59oxeDLE9fXx5tD86uvzIji74dmLaj3YI=";
           var GraphResourceId = "https://graph.microsoft.com";
           string userObjectId = controller.HttpContext.User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
           ClientCredential clientCred = new ClientCredential(ClientId, ClientSecret);
           AuthenticationContext authCOntext= new AuthenticationContext(Authority, new SampleSessionCache(userObjectId, controller.HttpContext.Session));
           AuthenticationResult result = await authContext.AcquireTokenSilentAsync(GraphResourceId,ClientId);
           GraphServiceClient client = new GraphServiceClient(new DelegateAuthenticationProvider(async request=>{
               request.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
               await Task.FromResult(0);
           }));
           return client;
       }
   }
}


有了上面的准备,在真正需要用到 Graph 服务的地方,我们的代码是非常简单的,请参考 HomeController.cs 文件中的 About 方法


[Authorize]//用这个标记该方法需要用户登录
public async Task About()
{
   var client = await this.GetAuthenticatedClient();  
   //获取用户详细信息,然后传递给视图
   return View(await client.Me.Request().GetAsync());
}

到这里为止,我的这个例子的主要代码就解释完了。你可能会觉得,这太简单了吧。如果你这样认为,我一方面感到很高兴,因为这是我希望呈现出来的效果;另一方面我要提醒你的是,由于 asp.net core 是一个还比较新的技术,这方面的材料相当少,其实我还是做了相当多的研究才精炼成这样的,其间遇到过多少坑,多少曲折迂回,不足以外人道也,但我很看好 asp.net core,并且将持续在此之上进行投资,这也几乎是可以肯定的。








4





结语



这个例子实现的功能并没有什么惊天动地的,但与咱们之前一系列的范例相呼应的是,我是要帮助大家打开 Microsoft Graph 的大门,至于你要怎么去利用里面丰富的宝藏,我就选择的权利交给你自己。


写到这里,我的这个系列文章的第一个大的里程碑应该是要实现了。我用了将近四个月的时间,写了十几篇跟 Office 365 开发入门,以及具体的 Microsoft Graph 开发有关的文章,差不多算是比较完整了。


我还将继续写后续的内容,例如 Office Add-ins,SharePoint 开发,Teams 开发(Bot 等),有兴趣的朋友们可继续关注。














 更新一








作为一个不断追求代码复用的程序猿,我这两天在上面这个范例基础上对代码进行了一定的封装,如果你此时查看代码的话,会发现已经有了较大的不同。


首先,我将所有公用的代码全部提取到了一个单独的项目(Office365GraphCoreMVCHelper )中,这里面的关键代码有

一个用来读取配置文件的类型


namespace Office365GraphCoreMVCHelper
{
   public class AppSetting
   {
       public Info Office365ApplicationInfo { get; set; }
       public class Info
       {
           public string ClientId { get; set; }
           public string ClientSecret { get; set; }
           public string Authority { get; set; }
           public string GraphResourceId { get; set; }
       }
   }
}


一个可公用的Startup类型


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Caching;
using Microsoft.AspNetCore.Authentication.COOKIEs;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Session;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace Office365GraphCoreMVCHelper
{
   public class Startup
   {
       static IConfigurationRoot Configuration { get; set; }
       public Startup(IHostingEnvironment env)
       {
           COnfiguration= new ConfigurationBuilder()
                           .SetBasePath(env.ContentRootPath)
                           .AddJsonFile("appsettings.json")
                           .Build();
       }
       // This method gets called by the runtime. Use this method to add services to the container.
       public void ConfigureServices(IServiceCollection services)
       {
           //这里将配置信息注入到应用程序中
           services.AddOptions();
           services.Configure(Configuration);
           services.AddSession();
           services.AddAuthentication(sharedoptiOns=> sharedoptions.SignInScheme = COOKIEAuthenticationDefaults.AuthenticationScheme);
           // Add framework services.
           services.AddMvc();
       }
       // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
       public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
       {
           loggerFactory.AddConsole();
           loggerFactory.AddDebug();
           if (env.IsDevelopment())
           {
               app.UseDeveloperExceptionPage();
               app.UseBrowserLink();
           }
           else
           {
               app.UseExceptionHandler("/Home/Error");
           }
           //ConfigureMiddleware(app,env,loggerFactory);
           app.UseStaticFiles();
           app.UseSession();
           app.UseCOOKIEAuthentication();
           //获得之前注入的配置信息
           var optiOns= app.ApplicationServices.GetRequiredService>();
           app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
           {
               ClientId = options.Value.Office365ApplicationInfo.ClientId,
               Authority = options.Value.Office365ApplicationInfo.Authority,
               ClientSecret = options.Value.Office365ApplicationInfo.ClientSecret,
               RespOnseType= OpenIdConnectResponseType.CodeIdToken,
               CallbackPath = "/signin-oidc",
               GetClaimsFromUserInfoEndpoint = true,
               Events = new OpenIdConnectEvents
               {
                   OnAuthorizationCodeReceived= async (context) =>
                   {
                       string userObjectId = (context.Ticket.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
                       ClientCredential clientCred = new ClientCredential(options.Value.Office365ApplicationInfo.ClientId, options.Value.Office365ApplicationInfo.ClientSecret);
                       AuthenticationContext authCOntext= new AuthenticationContext(options.Value.Office365ApplicationInfo.Authority, new SampleSessionCache(userObjectId, context.HttpContext.Session));
                       AuthenticationResult authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
                           context.ProtocolMessage.Code, new Uri(context.Properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey]), clientCred, options.Value.Office365ApplicationInfo.GraphResourceId);
                   }
               }
           });
           app.UseMvc(routes =>
           {
               routes.MapRoute(
                   name: "default",
                   template: "{cOntroller=Home}/{action=Index}/{id?}");
           });
       }
       private Task OnAuthenticationFailed(FailureContext context)
       {
           context.HandleResponse();
           context.Response.Redirect("/Home/Error?message=" + context.Failure.Message);
           return Task.FromResult(0);
       }
   }
}

改造过的SDKHelper类型,主要增加了从配置文件中读取信息的功能


using System;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Graph;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Builder;
namespace Office365GraphCoreMVCHelper
{
   public static class SDKHelper
   {
       public static async Task GetAuthenticatedClient(this ControllerBase controller,IOptions options)
       {
           var Authority = options.Value.Office365ApplicationInfo.Authority;
           var ClientId = options.Value.Office365ApplicationInfo.ClientId;
           var ClientSecret = options.Value.Office365ApplicationInfo.ClientSecret;
           var GraphResourceId = options.Value.Office365ApplicationInfo.GraphResourceId;
           string userObjectId = controller.HttpContext.User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
           ClientCredential clientCred = new ClientCredential(ClientId, ClientSecret);
           AuthenticationContext authCOntext= new AuthenticationContext(Authority, new SampleSessionCache(userObjectId, controller.HttpContext.Session));
           AuthenticationResult result = await authContext.AcquireTokenSilentAsync(GraphResourceId, ClientId);
           GraphServiceClient client = new GraphServiceClient(new DelegateAuthenticationProvider(async request =>
           {
               request.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
               await Task.FromResult(0);
           }));
           return client;
       }
   }
}


由于有了这个公用的组件,那么在aspnetcoremvc这个主程序中,我可以极大地简化代码。


首先,我在项目文件中定义了对公用组件的引用



 
   netcoreapp1.1
 

 
   
   
   
   
   
   
   
   
   
   
   
 

   
   
 
 


然后,我删除了该项目中的Startup类型,取而代之的在Program中直接引用公用组件中定义好的那个Startup


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Office365GraphCoreMVCHelper;
namespace aspntecoremvc
{
   public class Program
   {
       public static void Main(string[] args)
       {
           var host = new WebHostBuilder()
               .UseSetting("startupAssembly","Office365GraphCoreMVCHelper")
               .UseKestrel()
               .UseContentRoot(Directory.GetCurrentDirectory())
               .UseIISIntegration()
               .Build();
           host.Run();
       }
   }
}


当然,我们需要定义一个配置文件来保存clientId等信息,该文件命名为appsettings.json


{
   "Office365ApplicationInfo":{
       "ClientId":"e91ef175-e38d-4feb-b1ed-f243a6a81b93",
       "ClientSecret":"2F5jdoGGNn59oxeDLE9fXx5tD86uvzIji74dmLaj3YI=",
       "Authority":"https://login.microsoftonline.com/office365devlabs.onmicrosoft.com",
       "GraphResourceId":"https://graph.microsoft.com"
   }
}


最后,在HomeController(或者同类需要用到Microsoft Graph的Controller)中,通过下面的代码来实现调用


using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Graph;
using System.Net.Http.Headers;
using Office365GraphCoreMVCHelper;
using Microsoft.Extensions.Options;
namespace aspntecoremvc.Controllers
{
   public class HomeController : Controller
   {
       private readonly IOptions Options;
       public HomeController(IOptions options)
       {
           this.OptiOns= options;
       }
       public IActionResult Index()
       {
           return View();
       }
       [Authorize]
       public async Task About()
       {
           var client = await this.GetAuthenticatedClient(this.Options);            
           return View(await client.Me.Request().GetAsync());
       }
       public IActionResult Contact()
       {
           ViewData["Message"] = "Your contact page.";
           return View();
       }
       public IActionResult Error()
       {
           return View();
       }
   }
}











        
更新二







我进一步上面分离出来的这个Office365GraphCoreMVCHelper的项目打包成了一个nuget的package https://www.nuget.org/packages/Office365GraphCoreMVCHelper/,以便实现更大范围的复用。



如何使用它呢?很简单,请按照下面的步骤即可




  1. 创建一个ASP.NET Core MVC项目



    dotnet new mvc




  2. 增加对于Office365GraphCoreMVCHelper的引用,修改csproj文件,添加如下的定义





  3. 下载这个包



    dotnet restore




  4. 修改Program.cs文件,使用Office365GraphCoreMVCHelper 定义好的Startup类(增加下面代码中的UseSetting这一句)。当前项目的Startup.cs文件可以删除。


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
namespace testaspnetcoremvc
{
   public class Program
   {
       public static void Main(string[] args)
       {
           var host = new WebHostBuilder()
               .UseKestrel()
               .UseContentRoot(Directory.GetCurrentDirectory())
               .UseIISIntegration()
               .UseSetting("startupAssembly","Office365GraphCoreMVCHelper")
               .Build();
           host.Run();
       }
   }
}


修改appsettings.json文件,确保里面有如下Office365ApplicationInfo信息


{
 "Office365ApplicationInfo":{
   "ClientId":"e91ef175-e38d-4feb-b1ed-f243a6a81b93",
   "ClientSecret":"2F5jdoGGNn59oxeDLE9fXx5tD86uvzIji74dmLaj3YI=",
   "Authority":"https://login.microsoftonline.com/office365devlabs.onmicrosoft.com",
   "GraphResourceId":"https://graph.microsoft.com"
 },
 "Logging": {
   "IncludeScopes": false,
   "LogLevel": {
     "Default": "Warning"
   }
 }
}


修改HomeController,在需要进行身份验证以及调用Graph API的地方使用如下的代码即可

//添加几个引用
using Office365GraphCoreMVCHelper;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authorization;
//修改构造函数,接受注入的配置信息
private IOptions settings;
public HomeController(IOptions options)
{
   settings = options;
}
//在需要调用Graph API的Action中做如下修改
[Authorize]
public async Task About()
{
   var client = await this.GetAuthenticatedClient(settings);
   var user = client.Me.Request().GetAsync().Result;
   ViewData["Message"] = $"Hello,{user.DisplayName}";
   return View();
}

推荐阅读
  • 安装mysqlclient失败解决办法
    本文介绍了在MAC系统中,使用django使用mysql数据库报错的解决办法。通过源码安装mysqlclient或将mysql_config添加到系统环境变量中,可以解决安装mysqlclient失败的问题。同时,还介绍了查看mysql安装路径和使配置文件生效的方法。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
author-avatar
mobiledu2502858393
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有