DotNetCore Web应用程序中的Cookie管理

原文来自互联网,由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权,请联系小编,小编将在24小时内删除。限于译者的能力有限,个别语句翻译略显生硬,还请见谅。

作者简介:Jon(Jonathan)Seeley,一位资深.NET开发者,主要从事Asp.NET/Asp.NET CORE/WPF等技术栈的开发,他的博客地址为https://www.seeleycoder.com/。

原文链接[1]

对于那些习惯于在传统ASP.NET中使用Cookie的人来说,改用ASP.NET Core可能会让我们抓狂。在旧系统中,我们能够直接从请求和响应对象中添加和删除cookie(无论好坏)。这可能导致我们在请求期间多次写入和覆盖相同的cookie,因为不同部分的代码会影响它。DotNetCore改变了游戏规则,这是一件好事,相信我。今天,我们将学习DotNetCore Web应用程序中的cookie管理技术。

这篇文章的所有代码都可以在我的GitHub上找到[2]

了解过去

为了论证,我想介绍一下传统的ASP.NET MVC中用于加载Cookie的“通用”代码。当然,问题在于,如果代码中的某处设置了cookie值,而我们稍后又在寻找它,我们想确保我们始终获得最新的副本,而不必一定是请求中包含的内容。下面的代码看起来是否响应中首先匹配。

public static System.Web.HttpCookie GetCookie(this System.Web.HttpContextBase context, string keyName)
{System.Web.HttpCookieCollection cookies = new System.Web.HttpCookieCollection();System.Web.HttpCookie cookie = null;// check for response value first...if (context.Response.Cookies.AllKeys.Any(key => string.Equals(key, keyName, StringComparison.OrdinalIgnoreCase)))cookie = context.Response.Cookies.Get(keyName);else if (context.Request.Cookies.AllKeys.Any(key => string.Equals(key, keyName, StringComparison.OrdinalIgnoreCase)))cookie = context.Request.Cookies.Get(keyName);return cookie;
}

因此,这就是我们可能访问Cookie进行消费的方式,我们在修改过程中会不会无意中把这个流程搞乱了?我敢肯定,大家也许有很多方式,以下这是我可能做过的一个例子:

public static void SetCookie(this System.Web.HttpContextBase context, string keyName, string value, DateTime? expiry = null)
{if (context.Response.HeadersWritten)return;// a null value is equivalent to deletionif (value == null){context.Request.Cookies.Remove(keyName);context.Response.Cookies.Add(new System.Web.HttpCookie(keyName, "") { Expires = DateTime.Today.AddYears(-1) });return;}System.Web.HttpCookie newCookie = new System.Web.HttpCookie(keyName, value);if (expiry.HasValue)newCookie.Expires = expiry.Value;context.Response.Cookies.Add(newCookie);
}

在上面的代码中,我们试图确保删除cookie也可以防止在未找到同一请求的情况下尝试使用它。如果已经发送了标头,我们也将阻止编写cookie(因为它将引发异常)。该代码“不做”的一件事是防止重复,我是故意这样做的。一旦将其写到浏览器中,响应中的最后一个将调用,因此它仍将按预期“工作”,但同样,我们还有一个错误。如果您想知道,您不想随意,context.Response.Cookies.Add但是应该检查它是否已经存在,如果存在,请调用context.Response.SetCookie。

尽管编写一个cookie管理器并确保您所有的cookie代码都能通过它并不困难,但对于菜鸟和经验丰富的开发人员来说,普遍认为“它可以正常工作”是很常见的。从这个角度来说,如果您确实了解了Asp.NET中Cookie的设置方法并习惯了它,DotNetCore会让您失望。

DotNetCore的差异

既然我们已经介绍了一些您可能期望在传统的ASP.NET MVC中执行操作的方式,那么强调DotNetCore中的差异非常重要。

首先,HttpContext.Request.CookiesDotNetCore中的集合不能被修改。希望您在以前的示例中注意到,当我们删除传统版本的cookie时,我们也删除了请求副本,以确保以后不再使用无效的cookie。同样,HttpContext.Response.Cookies不允许您删除附加到该项目的项目。当然,您可以要求“删除” cookie,但这只是修改了到期时间,因此浏览器将其删除。一旦请求来了,就会调用这个方法。

当我用DotNetCore重写大型应用程序并从旧系统“复制”代码时,这些差异是我很早就遇到的,并导致了对ASP.NET Core中cookie管理的了解。

这些差异是一件好事,因为它们迫使您对正在做的事情多加思考,而不是仅仅假设一切正常。如果使用传统ASP.NET MVC的示例代码来设置Cookie,除非小心,否则最终可能会在响应中获得cookie的多个副本。

如果发生这种情况,并且您稍后尝试在同一请求中读取该值,则可能实际上并没有获得您希望的结果。这样的操作很糟糕。

介绍Cookie Service

鉴于我们之间的差异,再加上DotNetCore确实尽力让您使用依赖项注入这一事实,那么您将如何进行cookie管理?我个人认为,您所有的cookie管理都应通过服务进行分配,然后由中间件负责将最终状态写回到响应中。让我们开始吧:

public class CachedCookie
{public string Name { get; set; }public string Value { get; set; }public CookieOptions Options { get; set; }public bool IsDeleted { get; set; }
}
public interface ICookieService
{void Delete(string cookieName);T Get<T>(string cookieName, bool isBase64 = false) where T : class;T GetOrSet<T>(string cookieName, Func<T> setFunc, DateTimeOffset? expiry = null, bool isBase64 = false) where T : class;void Set<T>(string cookieName, T data, DateTimeOffset? expiry = null, bool base64Encode = false) where T : class;void WriteToResponse(HttpContext context);
}
public class CookieService : ICookieService
{private readonly HttpContext _httpContext;private Dictionary<string, CachedCookie> _pendingCookies = null;public CookieService(IHttpContextAccessor httpContextAccessor){_httpContext = httpContextAccessor.HttpContext;_pendingCookies = new Dictionary<string, CachedCookie>();}public void Delete(string cookieName){}public T Get<T>(string cookieName, bool isBase64 = false) where T : class{throw new NotImplementedException();}public T GetOrSet<T>(string cookieName, Func<T> setFunc, DateTimeOffset? expiry = null, bool isBase64 = false) where T : class{throw new NotImplementedException();}public void Set<T>(string cookieName, T data, DateTimeOffset? expiry = null, bool base64Encode = false) where T : class{}public void WriteToResponse(HttpContext context){}
}

在上面的代码块中,我添加了一个CachedCookie类, 对我们的接口进行了存根CookieService,并为我们的服务设置了框架。

我们早应了解的一件事是,由于某种原因,该服务基于泛型。我希望能够将几乎所有的价值写到我的cookie中。在这种情况下,我选择将泛型限制在一个类中(该类string可以限定,但所有基本值类型都将失败)。为了使这种魔术起作用,我将使用JSON将我的值序列化为字符串。

为了弄清楚所有部分如何组合在一起,我认为我们将一次迈出这一步。

我们的构造函数正在注入,IHttpContextAccessor这使我们能够访问HttpContext请求的当前值。这类似于我们曾经使用过的旧ASP.NET HttpContext.Current。但是,要使此方法起作用,我们需要将其注册,因此请跳至Startup.cs您的ConfigureServices方法并将这些行添加到您的方法中:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

services.AddScoped<ICookieService, CookieService>();

您还会在构造函数中注意到的另一件事是,我们正在为的实例设置一个空字典CachedCookie。在中间件将它们转储到响应之前,这是我们在请求期间跟踪cookie状态的地方。

中间件

我们需要照顾的下一件事是创建我们的中间件并将其放入我们的管道中。让我们添加CookieServiceMiddleware.cs并编写下列代码:

internal class CookieServiceMiddleware
{private readonly RequestDelegate _next;public CookieServiceMiddleware(RequestDelegate next){_next = next;}public async Task Invoke(HttpContext context, ICookieService cookieService){// write cookies to response right before it starts writing out from MVC/api responses...context.Response.OnStarting(() =>{// cookie service should not write out cookies on 500, possibly others as wellif (!context.Response.StatusCode.IsInRange(500, 599)){cookieService.WriteToResponse(context);}return Task.CompletedTask;});await _next(context);}
}

无法在构造函数级别将范围服务注入中间件。您会注意到,我在Invoke方法中[3]注入了它,这似乎有点像魔术。在DotNetCore底层的某个地方的IServiceProvider组件知道如何进行注入。要注意的另一件事是,我检测到响应何时开始,然后检查状态码是否不在特定范围内。如果超出该范围,那么我们将继续通过服务将Cookie写入响应中。该IsInRange扩展方法是一个我已经添加的话,事不宜迟,这里是一个基本的IntExtensions.cs添加到项目中我:

public static class IntExtensions
{public static bool IsInRange(this int checkVal, int value1, int value2){// First check to see if the passed in values are in order. If so, then check to see if checkVal is between themif (value1 <= value2)return checkVal >= value1 && checkVal <= value2;// Otherwise invert them and check the checkVal to see if it is between themreturn checkVal >= value2 && checkVal <= value1;}
}

注册中间件

好的 最后,我们需要添加到中间件代码中并进行连接。中间件的约定是创建一个静态类和扩展方法来处理中间件的注册。让我们添加CookieServiceMiddlewareExtensions:

public static class CookieServiceMiddlewareExtensions
{public static IApplicationBuilder UseCookieService(this IApplicationBuilder builder){return builder.UseMiddleware<CookieServiceMiddleware>();}
}

让我们进入Startup.cs进入我们的Configure方法中,并添加app.UseCookieService();到链的某处。这里的窍门是您希望它在您的app.UseMvc呼叫之前以及可能影响您响应的任何其他内容之前显示,但不要太高以至于过早地向响应写出cookie。在这种情况下,我选择在app.UseCookiePolicy通话后添加它。如果您有很多其他中间件,则您自己的工作量可能会有所不同。补充一下。如果我的中间件稍微复杂一点,并且有多个服务需要注册,那么我可能还创建了一个扩展方法来从我的ConfigureServices方法中调用。如果我正在创建一个用于分发的中间件,那么即使只有一个服务,我也绝对可以做到。我不想强迫某人必须了解一切,才能为DI配置我的中间件,他们应该能够简单地要求添加它并继续前进。

该扩展方法可能具有这样的签名public static IServiceCollection ConfigureCookieService(this IServiceCollection services, IConfiguration configuration)。(这里的IConfiguration是可选的……在某些方面我需要它,但是显然在这种情况下我们不需要它)。

实现

太好了,我们现在已经注册了我们的服务和中间件,但是它什么也没做。让我们继续,一次开始实现一种方法。由于我们实际上要尝试做的第一件事是加载cookie以供消费,也许我们应该从那里开始。进入CookieService.cs并将以下代码添加到public T Get方法中:

Get 

public T Get<T>(string cookieName, bool isBase64 = false)where T : class
{return ExceptionHandler.SwallowOnException(() =>{// check local cache first...if (_pendingCookies.TryGetValue(cookieName, out CachedCookie cookie)){// don't retrieve a "deleted" cookieif (cookie.IsDeleted)return default(T);return isBase64 ? Newtonsoft.Json.JsonConvert.DeserializeObject<T>(cookie.Value.FromBase64String()): Newtonsoft.Json.JsonConvert.DeserializeObject<T>(cookie.Value);}if (_httpContext.Request.Cookies.TryGetValue(cookieName, out string cookieValue))return isBase64 ? Newtonsoft.Json.JsonConvert.DeserializeObject<T>(cookieValue.FromBase64String()): Newtonsoft.Json.JsonConvert.DeserializeObject<T>(cookieValue);return default(T);});
}

在讨论之前,让我们先看一下这些内容ExceptionHandler。我们的Get方法首先询问我们的pendingCookies字典是否有与键匹配的东西。如果有,它将询问我们是否已对其进行标记IsDeleted。如果我们有一个并且未被删除,那么我们继续将其反序列化为请求的对象类型,并且可选地,我们需要首先从base64对其进行解码。如果我们在缓存中没有它的本地副本,那么我们继续看是否HttpContext.Request.Cookies具有它,并且像我们的本地缓存一样,可以选择在最终反序列化之前从base64解码。

现在,为什么我要对它进行base64编码?从本质上讲,我并不是要“保护”我的cookie免受窥视,但是,如果我有一个非常复杂的对象,我要写出一个cookie,我想对其进行分解。对象的JSON字符串表示形式可能非常笨拙。

说到base64编码…这些是我在StringExtensions.cs文件中添加的几个扩展方法。干得好:

public static class StringExtensions
{public static string FromBase64String(this string value, bool throwException = true){try{byte[] decodedBytes = System.Convert.FromBase64String(value);string decoded = System.Text.Encoding.UTF8.GetString(decodedBytes);return decoded;}catch (Exception ex){if (throwException)throw new Exception(ex.Message, ex);elsereturn value;}}public static string ToBase64String(this string value){byte[] bytes = System.Text.ASCIIEncoding.UTF8.GetBytes(value);string encoded = System.Convert.ToBase64String(bytes);return encoded;}
}

好吧,这是什么ExceptionHandler.SwallowOnException法术?我本可以使用该try {} catch {}块,但这是一个用例,其中我100%可以接受,失败只是因为存在cookie而已,因为cookie根本就不存在。现在……如果您深入研究该处理程序的代码,您会发现它仍在执行try / catch块,我只是对其进行了抽象。让我向您证明这一点。

异常处理程序

public static class ExceptionHandler
{public static T SwallowOnException<T>(Func<T> func){try{return func();}catch{return default(T);}}
}

Set

嘿,我们很酷,可以加载cookie,但是如果我们不能创建cookie,那不是很有用,对吧?让那部分起作用。

public void Set<T>(string cookieName, T data, DateTimeOffset? expiry = null, bool base64Encode = false)where T : class
{// info about cookieoptionsCookieOptions options = new CookieOptions(){Secure = _httpContext.Request.IsHttps};if (expiry.HasValue)options.Expires = expiry.Value;if (!_pendingCookies.TryGetValue(cookieName, out CachedCookie cookie))cookie = Add(cookieName);// always set options and value;cookie.Options = options;cookie.Value = base64Encode? Newtonsoft.Json.JsonConvert.SerializeObject(data).ToBase64String(): Newtonsoft.Json.JsonConvert.SerializeObject(data);
}

创建Cookie时,我们需要设置一些信息。我在这里几乎没有内容,但我强烈建议您阅读CookieOptions[4]。不设置Expires将默认为“会话” cookie。如果您将Google Chrome浏览器用于“始终打开”模式(或所谓的“笨拙”),则它们将无法正常工作。在这里的代码中,我们将查看是否已经有一个待处理的Cookie实例,如果没有,则添加一个实例。一分钟后,我将介绍该方法。在获得cookie实例之后,我们将附加选项并编写可选的以base64编码的值。Add现在让我们看一下该方法。

protected CachedCookie Add(string cookieName)
{var cookie = new CachedCookie{Name = cookieName};_pendingCookies.Add(cookieName, cookie);return cookie;
}

很基本的东西。我们只是放宽信任,我们可以添加它并添加它。该方法没有公开,所以我相信我不必先检查字典。如果您对此不满意,请随时进行修改。

删除Cookie

在某个时候,我们将要删除Cookie,对吗?我们希望确保对同一cookie的后续查询都知道它已被删除,正如我们在Get调用中所看到的那样。为了使它正常工作,我们需要本地缓存来跟踪它。

void ICookieService.Delete(string cookieName)
{Delete(cookieName);
}
protected CachedCookie Delete(string cookieName)
{if (_pendingCookies.TryGetValue(cookieName, out CachedCookie cookie))cookie.IsDeleted = true;else{cookie = new CachedCookie{Name = cookieName,IsDeleted = true};_pendingCookies.Add(cookieName, cookie);}return cookie;
}

在上面的代码中,我们具有接口Delete方法和类Delete方法,它们都具有相同的签名。我可以给他们起个不同的名字,但我真的不想这么做。但是,为了防止编译器报错,我们必须将接口方法设为显式接口调用。我们只需将该调用传递到我们的类实例方法中。进入类实例delete方法后,我们将查看是否已经有一个暂挂实例,如果有,请将其标记为已删除。如果没有,我们将其添加到缓存中并标记为已删除。

GetOrSet 

有时,您希望Cookie不管存在如何,但是如果已经存在,那么您就想获得它的价值。一个用例是如果您要加载cookie(如果存在)或设置默认值。在我工作过的一个站点上,我们有一个适合该用例的“行程计划器”。我想知道他们的详细信息(如果有的话),否则我将设置一些默认值,以便其余的会话体验基于相同的信息。设置非常简单:

public T GetOrSet<T>(string cookieName, Func<T> setFunc, DateTimeOffset? expiry = null, bool isBase64 = false)where T : class
{T cookie = Get<T>(cookieName, isBase64);if (cookie != null)return cookie;T data = setFunc();Set(cookieName, data, expiry, isBase64);return data;
}

如果cookie存在,我们得到它。如果没有,我们将其设置。十分简单。

输出

如果我们从不将其写回响应中,那么以上所有代码实际上都没有关系,对吗?还记得在context.Response.OnStarting我们告诉服务期间在中间件中执行的服务WriteToResponse吗?让我们现在实际做点什么:

public void WriteToResponse(HttpContext context)
{foreach (var cookie in _pendingCookies.Values){if (cookie.IsDeleted)context.Response.Cookies.Delete(cookie.Name);elsecontext.Response.Cookies.Append(cookie.Name, cookie.Value, cookie.Options);}
}

我们重复我们的每一个悬而未决的饼干和要么Delete或者Append他们根据我们的缓存值。现在我们只写出每个cookie的一个副本,而不是我们在本文开头介绍的经典ASP.NET崩溃。

与测试代码一起实现

GitHub上的代码在HomeController中有一个相当蹩脚的小演示。接下来是一些单元测试。在发布一些代码之前,我想回顾一下我的BaseTest.cs工作方式。我可以(坦率地说应该有),但是由于我从生产代码中复制了这个代码,而这个代码还有其他问题,所以我没有使用)DotNetCore服务集合。相反,BaseTest依赖于UnityContainer。对于我而言,这是设置依赖项引擎的一种非常简单的方法。你想怎么嘲笑就怎么嘲笑吧。

随之而来的将是CookieServiceTests类的一大堆垃圾。该Initialize方法设置了每个测试将要使用的内容,然后每个单独的测试都设置了自己的场景。如何使用该服务应该变得显而易见,并希望为您提供一些如何在自己的项目中使用该服务的想法。

[TestClass]
public class CookieServiceTests : BaseTest
{IHttpContextAccessor _httpContextAccessor;HttpContext _httpContext;CookieService _target;[TestInitialize]public void Initialize(){_httpContextAccessor = Substitute.For<IHttpContextAccessor>();_httpContext = new DefaultHttpContext();_httpContextAccessor.HttpContext.Returns(_httpContext);Container.RegisterInstance(_httpContextAccessor);_target = Container.Resolve<CookieService>();}[TestMethod]public void CookieService_SetCookie_Success(){CookieFake cookie = new CookieFake { TestProperty = 25, TestPropertyString = "blah" };_target.Set("fakecookie", cookie);CookieFake cachedCookie = _target.Get<CookieFake>("fakecookie");Assert.IsNotNull(cachedCookie);Assert.AreEqual(cookie.TestProperty, cachedCookie.TestProperty);Assert.AreEqual(cookie.TestPropertyString, cachedCookie.TestPropertyString);}[TestMethod]public void CookieService_SetCookie_StringOnly_Success(){string value = "I'm a cookie value";_target.Set("fakecookie", value);string result = _target.Get<string>("fakecookie");Assert.IsFalse(string.IsNullOrWhiteSpace(result));Assert.AreEqual(value, result);}[TestMethod]public void CookieService_SetCookie_Base64_Success(){CookieFake cookie = new CookieFake { TestProperty = 25, TestPropertyString = "blah" };_target.Set("fakecookie", cookie, base64Encode: true);CookieFake cachedCookie = _target.Get<CookieFake>("fakecookie", true);Assert.IsNotNull(cachedCookie);Assert.AreEqual(cookie.TestProperty, cachedCookie.TestProperty);Assert.AreEqual(cookie.TestPropertyString, cachedCookie.TestPropertyString);}[TestMethod]public void CookieService_GetOrSetCookie_SetsCookie_Success(){Func<CookieFake> createCookie = () =>{return new CookieFake { TestProperty = 25, TestPropertyString = "blah" };};var cookie = _target.GetOrSet<CookieFake>("fakecookie", createCookie);Assert.IsNotNull(cookie);Assert.AreEqual(cookie.TestProperty, 25);}[TestMethod]public void CookieService_GetOrSetCookie_GetsCookie_Success(){CookieFake cookie = new CookieFake { TestProperty = 25, TestPropertyString = "blah" };_target.Set("fakecookie", cookie);Func<CookieFake> createCookie = () =>{return new CookieFake { TestProperty = 55, TestPropertyString = "blah2" };};var retrievedCookie = _target.GetOrSet<CookieFake>("fakecookie", createCookie);Assert.IsNotNull(retrievedCookie);Assert.AreEqual(retrievedCookie.TestProperty, cookie.TestProperty);Assert.AreEqual(retrievedCookie.TestPropertyString, cookie.TestPropertyString);}[TestMethod]public void CookieService_GetCookie_Fail(){CookieFake cachedCookie = _target.Get<CookieFake>("fakecookie");Assert.IsNull(cachedCookie);}[TestMethod]public void CookieService_GetCookie_Base64_Fail(){CookieFake cookie = new CookieFake { TestProperty = 25, TestPropertyString = "blah" };_target.Set("fakecookie", cookie);CookieFake cachedCookie = _target.Get<CookieFake>("fakecookie", true);Assert.IsNull(cachedCookie);}
}
public class CookieFake
{public int TestProperty { get; set; }public string TestPropertyString { get; set; }
}

结论

DotNetCore Web应用程序中的Cookie管理并不是一件复杂的事情,但是很容易使效率低下。我们通过引入CookieService和中间件,研究了一种确保响应尽可能干净的方法。

今天发布的所有代码都可以在我的GitHub上找到[5]

我鼓励您查看整个项目,查看我在Web应用程序中蹩脚的示例,我相信你能从中学到有用的知识。

References

[1] 原文链接: https://www.seeleycoder.com/blog/cookie-management-asp-net-core/
[2] 我的GitHub上找到: https://github.com/fooberichu150/CookieService
[3] Invoke方法中: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1#service-lifetimes
[4] 您阅读CookieOptions: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.cookieoptions?view=aspnetcore-2.1
[5] 我的GitHub上找到: https://github.com/fooberichu150/CookieService

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311065.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

逆向so_记一次APP的so层算法逆向(七)

“ 前言&#xff1a;初学逆向 请多多指教 好累 感觉每天这样肝 人有点受不了了...”学习到的内容—1、新学习到IDA的一些分析时候的小技巧2、算法还原代码实现的练习(有个参数没有分析出来&#xff0c;后面知道了会补上的)3、在Frida中使用命令行调试的方便方法分析过程—APP登…

C++实现Huffman树

代码如下&#xff1a; #include <iostream> using namespace std; int s1, s2;typedef struct {int weight;int parent, lch, rch; } HTNode, *HuffmanTree;void Select(HuffmanTree &HT, int n, int &s1, int &s2) {int minv;//定义一个临时变量存储最小值…

.NET Core开发实战(第25课:路由与终结点:如何规划好你的Web API)--学习笔记(下)...

25 | 路由与终结点&#xff1a;如何规划好你的Web API自定义约束实现了路由约束接口&#xff0c;它只有一个 Match 方法&#xff0c;这个方法传入了 Http 当前的 httpContext&#xff0c;route&#xff0c;routeKey这个 routeKey 就是我们要验证的 key 值后面两个参数 RouteVal…

微软 Visual Studio 2019 16.5 发布:.NET 移动开发、生产力

微软最新发布了 Visual Studio 2019 16.5 版本&#xff0c;下面来看看主要更新内容&#xff1a;.NET 移动开发首先要讨论的特性是 XAML Hot Reload for Xamarin.Forms。此功能可加快开发速度&#xff0c;并使开发者可以更轻松地在移动应用的用户界面上进行构建、实验和迭代。且…

chrome主题_谷歌Chrome将很快允许用户创建自定义主题

站长之家(ChinaZ.com) 7月31日 消息:据9to5google报道&#xff0c;虽然用户可以通过Chrome Web Store定制主题&#xff0c;但用户要根据自己的独特喜好定制主题却不是一个简单的事。谷歌正寻求通过在Chrome内置一个自定义主题生成器来解决这个问题。Chrome Web Store中有许多传…

使用Magicodes.IE.Excel完成Excel图片的导入和导出

说明本章教程主要说明如何使用Magicodes.IE.Excel进行图片的导入导出。要点配置DTO进行Excel图片导出配置DTO进行Excel图片导入图片导入导出特性说明ExportImageFieldAttributeHeight&#xff1a;高度(默认15)Width&#xff1a;宽度(默认50)Alt&#xff1a;图片不存在时替换文本…

C++未定义行为-数组越界

我们先来看看下面的代码&#xff1a; #include <iostream> using namespace std; const int N 100010; int a[N]; int main() {for (int i 1;i<N;i) a[i] 2;return 0; }当我们写这段代码的时候&#xff0c;编译器就会发生这样的问题。 这是为什么呢&#xff1f;&a…

SuperBenchmarker一个用.NET编写的压测工具

0x01 前言在这之前想必大家对ab(http)与abs(https)也有一些了解,我们今天不去看ab和abs,SuperBenchmarker(sb.exe)是一个压测工具,他是一个受Apache Benchmark的启发,他会在终端窗口为我们显示最终的结果,同时也会在web界面生成一个动态结果。SuperBenchmarker(sb.exe)可以在Wi…

mysql文献综述_文献综述随笔(二十)

一、基本信息标题&#xff1a;中小型酒店管理系统的设计与实现时间&#xff1a;2013来源&#xff1a;厦门大学关键词&#xff1a;MVC;B/S;JAVA EE;JSP;MySQL;瀑布开发模型二、研究内容1.主要内容&#xff1a;系统业务需求、功能需求、系统架构设计、数据库设计1.1功能模块设计&…

五分钟完成 ABP vNext 通讯录 App 开发

ABP vNext&#xff08;后文简称Abp&#xff09;是 Volo 公司堪称艺术品级的应用开发框架&#xff0c;它基于领域驱动设计&#xff08;DDD&#xff09;的思维&#xff0c;创新地采用了模块化的设计。Abp 目前无疑是 ASP.NET Core 开发框架中最先进和最优雅的存在。笔者认为&…

mysql 5.74安装教程_MySQL安装、基本账户安全(5.0以后版本)

-----------MySQL 5.0以后版本的安装-----------MySQL安装安装包学习的必杀绝技——就是阅读包的安装说明(readme & install)文档。----------# rm /etc/my.cnf (安装前执行一下)----------1.Mysql-5.0.40.tar.gz1.1.Source Installation Overview(lines 74 of …

使用GUI工具Portainer.io管控Docker容器

背景5年前容器技术扑面而来&#xff0c;如今已经成为面向云原生开发的基础架构&#xff0c;基于微服务的设计需要部署大量容器&#xff0c;同时强调了友好快速的管理容器。是时候推荐一个轮子Portainer.io&#xff1a;提供GUI界面的容器管理工具&#xff0c;给开发者的工具箱又…

【项目升级】集成Quartz.Net Job实现(一)

这两天的新闻也是越来越多了&#xff0c;不仅Github接手了NPM&#xff0c;还有.NET 5也要新鲜出炉了&#xff08;11月正式发布&#xff09;&#xff0c;当然还有MVP峰会也正在如火如荼的展开&#xff0c;会有哪些好的东西被碰撞出来&#xff0c;也是很期待的。这些天我也简单的…

DevC++如何安装自定义头文件并使用

首先我们打开DevC&#xff0c;然后点击新建。 新建一个控制台应用程序 取一个喜欢的文件名。 然后会出现如下界面。 点击新建单元 将头文件源码放入 找到空白位置&#xff0c;右键&#xff0c;然后点关闭并保存 保存的文件名要为头文件的名字 使用这个头文件时&#xff0c;只…

论ORM之EFCore初篇(快速基于本地数据库实现数据操作)

欢迎大家阅读《朝夕Net社区技术专刊》第6期我们致力于.NetCore的推广和落地&#xff0c;为更好的帮助大家学习&#xff0c;方便分享干货&#xff0c;特创此刊&#xff01;很高兴你能成为忠实读者&#xff0c;文末福利不要错过哦&#xff01;前言&#xff1a;今天准备带大家一站…

C++变量的初始化问题及列表初始化

在C中&#xff0c;初始化是一个异常复杂的问题&#xff0c;很多人认为初始化是赋值的一种&#xff0c;事实上&#xff0c;初始化和赋值是两个完全不同的操作。 列表初始化 要定义一个名为haif的int变量并初始化为0&#xff0c;以下4条语句都可以做到。 int haif 0; int hai…

c++ set 遍历_47. Set 是如何工作的(3) 遍历顺序是如何确定的?

Set 是无序容器&#xff0c;它的插入顺序与迭代&#xff08;或者 print&#xff09;输出的顺序不保证与插入顺序一致&#xff0c;与 Dict 类似的问题&#xff0c;Set 的输出顺序是如何决定的呢&#xff1f;首先我们从 Set 的输出开始寻找蛛丝马迹&#xff0c;在 Dict 的研究中&…

跟着“土牛”学架构知识

这里的土牛是指abp的作者&#xff0c;土耳其人&#xff0c;简称“土牛”&#xff0c;前两天看了他分享的ppt&#xff0c;这里做个小笔记。架构分层图一&#xff08;abp作者&#xff09;图二&#xff08;clean架构&#xff09;图三&#xff08;在朋友圈看到的&#xff09;每种架…

《C++ Primer》2.6.1节练习

练习2.39&#xff1a; 在类体花括号后加一个分号就好了。 练习2.40&#xff1a; 代码如下&#xff1a; struct Sales_data{std::string bookNo;//书籍编号unsigned units_sold 0;//销售量double sellingprice 0.0;//零售价double saleprice 0.0//实售价double discount 0…

Cef mysql.exe_CEF3.2623使用记录:windows编译

CEF3.2623使用记录&#xff1a;windows编译1&#xff1a;cef3.2623下载地址2623是cef3最后一个支持xp系统的版本&#xff0c;且可以支持html的audio标签&#xff0c;可以用作对html音频的处理下载地址为 https://bitbucket.org/chromiumembedded/cef/branch/2623。下载win32版本…