您好 @MS XIE,
正如之前告诉您的一样,目前已经不提供下载这个示例,且后期也没有此计划。 如果您一定要实现此需求,你可以参照此文档 为 URL 重写模块开发自定义重写提供程序 来实现。
具体的操作步骤如下
- 创建一个Class Library类库项目,添加如下的文件。
- 一共有6个.cs文件,一个.resx 文件。具体代码将放到答案的最后。
- 文件添加后,进行编译,编译成功后,点击属性进行签名。可以创建新的不需要设置密码。
- 编译成功后到路径下 C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools,找到gacutil.exe 和 gacutil.exe.config 文件,复制我们的新建临时文件夹中。
- 找到新建的类库项目的编译后的文件.dll。
- 然后将三个文件复制到对应的服务器或者目标主机中。执行以下的命令。
> .\gacutil.exe /i url-rewrite-extensibility.dll > iisreset
- 然后检查是否可以添加Provider。
类库项目中需要的代码如下:
1.DbProvider.cs
//
// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
//
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Threading;
using Microsoft.Web.Iis.Rewrite;
using Microsoft.Web.Iis.Rewrite.Providers;
public sealed class DbProvider : IRewriteProvider, IProviderDescriptor, IDisposable {
private string connectionString;
private string storedProcedure;
private Timer timer;
private IRewriteContext rewriteContext;
public void Initialize(IDictionary<string, string> settings, IRewriteContext rewriteContext) {
this.rewriteContext = rewriteContext;
if (!settings.TryGetValue(SettingNames.ConnectionString, out this.connectionString) || string.IsNullOrEmpty(this.connectionString)) {
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ProviderSettingExpectedFormat, SettingNames.ConnectionString));
}
if (!settings.TryGetValue(SettingNames.StoredProcedure, out this.storedProcedure) || string.IsNullOrEmpty(this.storedProcedure)) {
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ProviderSettingExpectedFormat, SettingNames.StoredProcedure));
}
// CacheMinutesInterval is an optional parameter.
string minutesString;
if (settings.TryGetValue(SettingNames.CacheMinutesInterval, out minutesString))
{
int minutes = 0;
if (!string.IsNullOrEmpty(minutesString) && int.TryParse(minutesString, out minutes) && minutes > 0)
{
int period = minutes * 60 * 1000;
this.timer = new Timer(TimerCallback, null, period, period);
}
}
}
[SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")]
public string Rewrite(string value) {
using (SqlConnection connection = new SqlConnection(this.connectionString)) {
using (SqlCommand command = connection.CreateCommand()) {
command.Parameters.Add("@Input", SqlDbType.NVarChar).Value = value;
command.CommandType = CommandType.StoredProcedure;
command.CommandText = this.storedProcedure;
connection.Open();
return (string)command.ExecuteScalar();
}
}
}
public IEnumerable<SettingDescriptor> GetSettings() {
yield return new SettingDescriptor(SettingNames.ConnectionString, SettingNames.ConnectionStringFriendlyName);
yield return new SettingDescriptor(SettingNames.StoredProcedure, SettingNames.StoredProcedureFriendlyName);
yield return new SettingDescriptor(SettingNames.CacheMinutesInterval, SettingNames.CacheMinutesIntervalFriendlyName);
}
private void TimerCallback(object state) {
if (this.rewriteContext != null) {
this.rewriteContext.ClearRewriteCache();
}
}
public void Dispose() {
if (this.timer != null) {
this.timer.Dispose();
this.timer = null;
}
this.rewriteContext = null;
GC.SuppressFinalize(this);
}
}
2.FileBaseProvider.cs
//
// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
//
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using Microsoft.Web.Iis.Rewrite;
using Microsoft.Web.Iis.Rewrite.Providers;
public abstract class FileBaseProvider : IRewriteProvider, IProviderDescriptor, IDisposable {
private FileInfo file;
private bool ignoreCase;
private char separator = '\t';
private bool ready;
private FileSystemWatcher sharedWatcher;
private IRewriteContext rewriteContext;
private ReaderWriterLock fileAccessLock = new ReaderWriterLock();
public FileBaseProvider() {
}
~FileBaseProvider() {
Dispose(false);
}
protected FileInfo File {
get {
return this.file;
}
}
protected bool IgnoreCase {
get {
return this.ignoreCase;
}
}
protected char Separator {
get {
return this.separator;
}
}
public void Initialize(IDictionary<string, string> settings, IRewriteContext rewriteContext) {
string filePath;
if (!settings.TryGetValue(SettingNames.FilePath, out filePath) || string.IsNullOrEmpty(filePath)) {
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ProviderSettingExpectedFormat, SettingNames.FilePath));
}
// Use FileInfo to verify the file name.
this.file = new FileInfo(filePath);
if (!this.file.Exists) {
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.FilePathDoesNotExistFormat, this.file.FullName));
}
string ignoreCase;
// IgnoreCase is an optional parameter.
if (settings.TryGetValue(SettingNames.IgnoreCase, out ignoreCase) &&
!string.IsNullOrEmpty(ignoreCase)) {
if (ignoreCase.Equals("1", StringComparison.Ordinal)) {
this.ignoreCase = true;
}
else if (!ignoreCase.Equals("0", StringComparison.Ordinal)) {
this.ignoreCase = bool.Parse(ignoreCase);
}
}
string separator;
if (settings.TryGetValue(SettingNames.CharSeparator, out separator)) {
separator = separator.Trim();
if (separator.Length > 0) {
this.separator = separator[0];
}
}
this.rewriteContext = rewriteContext;
this.sharedWatcher = FileSystemWatcherPool.Instance.GetWatcher(this.file);
this.sharedWatcher.Changed += OnFileChange;
this.sharedWatcher.Deleted += OnFileDeleted;
this.sharedWatcher.Renamed += OnFileRenamed;
this.sharedWatcher.Created += OnFileCreated;
}
public string Rewrite(string input) {
if (!this.ready) {
this.fileAccessLock.AcquireWriterLock(Timeout.Infinite);
try {
if (!this.ready) {
LoadTableFromFile();
this.sharedWatcher.EnableRaisingEvents = true;
this.ready = true;
}
}
finally {
this.fileAccessLock.ReleaseWriterLock();
}
}
if (string.IsNullOrEmpty(input)) {
return string.Empty;
}
this.fileAccessLock.AcquireReaderLock(Timeout.Infinite);
try {
return this.Map(input);
}
finally {
this.fileAccessLock.ReleaseReaderLock();
}
}
public IEnumerable<SettingDescriptor> GetSettings() {
yield return new SettingDescriptor(SettingNames.FilePath, SettingNames.FilePathFriendlyName);
yield return new SettingDescriptor(SettingNames.IgnoreCase, SettingNames.IgnoreCaseFriendlyName);
foreach (SettingDescriptor value in GetAdditionalSettings()) {
yield return value;
}
}
private void OnFileChange(object sender, FileSystemEventArgs e) {
InvalidateRewriteCache();
}
private void OnFileCreated(object sender, FileSystemEventArgs e) {
InvalidateRewriteCache();
}
private void OnFileRenamed(object sender, RenamedEventArgs e) {
InvalidateRewriteCache();
}
private void OnFileDeleted(object sender, FileSystemEventArgs e) {
InvalidateRewriteCache();
}
private void InvalidateRewriteCache() {
if (this.ready) {
rewriteContext.ClearRewriteCache();
this.ready = false;
}
}
protected abstract void LoadTableFromFile();
protected abstract string Map(string input);
protected abstract IEnumerable<SettingDescriptor> GetAdditionalSettings();
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
if (this.sharedWatcher != null) {
this.sharedWatcher.Changed -= OnFileChange;
this.sharedWatcher.Deleted -= OnFileDeleted;
this.sharedWatcher.Renamed -= OnFileRenamed;
this.sharedWatcher.Created -= OnFileCreated;
this.sharedWatcher = null;
}
}
}
private sealed class FileSystemWatcherPool {
private Dictionary<string, WeakReference> watchers = new Dictionary<string, WeakReference>(StringComparer.OrdinalIgnoreCase);
private ReaderWriterLock watchersLock = new ReaderWriterLock();
private Timer cleanupTimer;
private const int OneMinute = 60 * 1000;
private static FileSystemWatcherPool sInstance;
private static object sLock = new object();
private FileSystemWatcherPool() {
this.cleanupTimer = new Timer(InvalidateWatcherList, null, OneMinute, OneMinute);
}
public static FileSystemWatcherPool Instance {
get {
if (sInstance == null) {
lock (sLock) {
if (sInstance == null) {
sInstance = new FileSystemWatcherPool();
}
}
}
return sInstance;
}
}
public FileSystemWatcher GetWatcher(FileInfo file) {
WeakReference reference;
FileSystemWatcher instance = null;
this.watchersLock.AcquireReaderLock(Timeout.Infinite);
try {
if (this.watchers.TryGetValue(file.FullName, out reference) && reference.IsAlive) {
instance = (FileSystemWatcher)reference.Target;
}
}
finally {
this.watchersLock.ReleaseReaderLock();
}
if (instance != null) {
return instance;
}
// Create a new one and add it to the pool.
instance = new FileSystemWatcher();
instance.Path = file.DirectoryName;
instance.Filter = file.Name;
instance.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
this.watchersLock.AcquireWriterLock(Timeout.Infinite);
try {
this.watchers[file.FullName] = new WeakReference(instance);
}
finally {
this.watchersLock.ReleaseWriterLock();
}
return instance;
}
private void InvalidateWatcherList(object state) {
const int Threshold = 50;
this.watchersLock.AcquireWriterLock(Timeout.Infinite);
try {
if (this.watchers.Count > Threshold) {
this.watchers = new Dictionary<string, WeakReference>();
}
}
finally {
this.watchersLock.ReleaseWriterLock();
}
}
}
}
3.FileContainsProvider.cs
//
// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
//
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Web.Iis.Rewrite;
public sealed class FileContainsProvider : FileBaseProvider {
private List<string> values;
protected override void LoadTableFromFile() {
this.values = new List<string>();
using (StreamReader reader = this.File.OpenText()) {
string line;
while ((line = reader.ReadLine()) != null) {
string trimmed = line.Trim();
if (trimmed.Length > 0) {
string value = this.IgnoreCase ? trimmed.ToLowerInvariant() : trimmed;
this.values.Add(value);
}
}
}
}
protected override string Map(string input) {
string inputToLower = this.IgnoreCase ? input.ToLowerInvariant() : input;
foreach (string value in this.values) {
if (inputToLower.Contains(value)) {
return value;
}
}
return string.Empty;
}
protected override IEnumerable<SettingDescriptor> GetAdditionalSettings() {
// No additional settings.
yield break;
}
protected override void Dispose(bool disposing) {
base.Dispose(disposing);
}
}
4.FileMapProvider.cs
//
// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
//
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Web.Iis.Rewrite;
public sealed class FileMapProvider : FileBaseProvider {
private Dictionary<string, string> dictionary;
protected override void LoadTableFromFile() {
this.dictionary = new Dictionary<string, string>(this.IgnoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
using (StreamReader reader = this.File.OpenText()) {
string line;
while ((line = reader.ReadLine()) != null) {
string[] keyValue = line.Split(base.Separator);
string key = keyValue[0].Trim();
if (!string.IsNullOrEmpty(key)) {
string value = keyValue.Length > 1 ? keyValue[1].Trim() : string.Empty;
this.dictionary.Add(key, value);
}
}
}
}
protected override string Map(string input) {
string output;
if (this.dictionary.TryGetValue(input, out output)) {
return output;
}
return string.Empty;
}
protected override IEnumerable<SettingDescriptor> GetAdditionalSettings() {
yield return new SettingDescriptor(SettingNames.CharSeparator, SettingNames.CharSeparatorFriendlyName);
}
protected override void Dispose(bool disposing) {
base.Dispose(disposing);
}
}
5.SettingNames.cs
//
// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
//
using System;
internal static class SettingNames {
public const string FilePath = "FilePath";
public const string FilePathFriendlyName = "File path";
public const string IgnoreCase = "IgnoreCase";
public const string IgnoreCaseFriendlyName = "Ignore case";
public const string CharSeparator = "Separator";
public const string CharSeparatorFriendlyName = "Separator character";
public const string ConnectionString = "ConnectionString";
public const string ConnectionStringFriendlyName = "SQL Server connection string";
public const string StoredProcedure = "StoredProcedure";
public const string StoredProcedureFriendlyName = "Stored procedure name";
public const string CacheMinutesInterval = "CacheMinutesInterval";
public const string CacheMinutesIntervalFriendlyName = "Cache minutes interval";
}
6.Resources.Designer.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30128.1
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.Web.Iis.Rewrite.Providers {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Web.Iis.Rewrite.Providers.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Rewrite file provider exception. The file "{0}" does not exist..
/// </summary>
internal static string FilePathDoesNotExistFormat {
get {
return ResourceManager.GetString("FilePathDoesNotExistFormat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The rewrite provider requires the setting "{0}"..
/// </summary>
internal static string ProviderSettingExpectedFormat {
get {
return ResourceManager.GetString("ProviderSettingExpectedFormat", resourceCulture);
}
}
}
}
7.Resources.resx
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="FilePathDoesNotExistFormat" xml:space="preserve">
<value>Rewrite file provider exception. The file "{0}" does not exist.</value>
</data>
<data name="ProviderSettingExpectedFormat" xml:space="preserve">
<value>The rewrite provider requires the setting "{0}".</value>
</data>
</root>
请注意所有文件的文件名和后缀名。
如果答案是正确的解决方案,请点击“接受答案”并投赞成票。如果您对此答案有其他问题,请点击“评论”。
注意:如果您想接收此线程的相关电子邮件通知,请按照我们的 文档 中的步骤启用电子邮件通知。
Best Regards
Xudong Peng