Heim > Backend-Entwicklung > C#.Net-Tutorial > [ASP.NET MVC Mavericks Road]11 – Filter

[ASP.NET MVC Mavericks Road]11 – Filter

黄舟
Freigeben: 2016-12-30 16:17:16
Original
1423 Leute haben es durchsucht

[ASP.NET
MVC Mavericks Road] 11 - Filter

Filter (Filter) basiert auf dem AOP-Design (Aspektorientierte Programmierung). Seine Funktion besteht darin, Kunden des MVC-Frameworks Inject zu verarbeiten zusätzliche Logik in Nebenanfragen integrieren, um übergreifende Anliegen auf sehr einfache und schöne Weise umzusetzen. Querschnittsbelange beziehen sich auf Funktionen, die sich über mehrere oder sogar alle Module des Programms erstrecken, darunter Protokollierung, Cache-Verarbeitung, Ausnahmebehandlung und Berechtigungsüberprüfung. In diesem Artikel wird die Erstellung und Verwendung verschiedener vom MVC-Framework unterstützter Filtertypen sowie die Steuerung ihrer Ausführung vorgestellt.

Inhalt dieses Artikels


Übersicht über vier grundlegende Filter

Vom MVC-Framework unterstützte Filter können in vier Kategorien eingeteilt werden Jede Kategorie kann zu unterschiedlichen Zeitpunkten bei der Verarbeitung von Anforderungen zusätzliche Logikverarbeitung einführen. Diese vier Filtertypen sind wie folgt:

[ASP.NET MVC Mavericks Road]11 – Filter

Bevor das MVC-Framework acion aufruft, ermittelt es zunächst, ob die Funktionen der Schnittstelle in der obigen Tabelle implementiert sind , dann request Der entsprechende Punkt in der Pipeline ruft die im Attribut definierte Methode auf.

Das MVC-Framework implementiert Standardattributklassen für diese Arten von Filterschnittstellen. Wie in der Tabelle oben gezeigt, implementiert die ActionFilterAttribute-Klasse zwei Schnittstellen, IActionFilter und IResultFilter. Diese Klasse ist eine abstrakte Klasse und muss implementiert werden. Die anderen beiden Attributklassen AuthorizeAttribute und HandleErrorAttribute stellen bereits einige nützliche Methoden bereit, die direkt verwendet werden können.

Filter können auf eine einzelne Aktivierungsmethode oder auf den gesamten Controller angewendet werden, und mehrere Filter können auf Aktion und Controller angewendet werden. Wie unten gezeigt:

[Authorize(Roles="trader")]  // 对所有action有效
public class ExampleController : Controller { 
 
    [ShowMessage]  // 对当前ation有效
    [OutputCache(Duration=60)] // 对当前ation有效
    public ActionResult Index() { 
        // ...  
    } 
}
Nach dem Login kopieren

Beachten Sie, dass bei einer benutzerdefinierten Controller-Basisklasse der auf die Basisklasse angewendete Filter auch für alle Unterklassen wirksam ist, die von der Basisklasse erben.


Autorisierungsfilter

Autorisierungsfilter wird vor Aktionsmethoden und anderen Filtertypen ausgeführt. Seine Aufgabe besteht darin, Berechtigungsrichtlinien durchzusetzen und sicherzustellen, dass Aktionsmethoden nur von autorisierten Benutzern aufgerufen werden können. Die vom Autorisierungsfilter implementierte Schnittstelle lautet wie folgt:

namespace System.Web.Mvc {
    public interface IAuthorizationFilter { 
        void OnAuthorization(AuthorizationContext filterContext); 
    } 
}
Nach dem Login kopieren

Benutzerdefinierter Autorisierungsfilter

Sie können die IAuthorizationFilter-Schnittstelle selbst implementieren, um Ihre eigene Sicherheitsauthentifizierungslogik zu erstellen, dies ist jedoch im Allgemeinen nicht erforderlich und wird nicht empfohlen. Wenn Sie die Sicherheitsauthentifizierungsrichtlinie anpassen möchten, ist es sicherer, die Standardklasse AuthorizeAttribute zu erben.

Wir demonstrieren den benutzerdefinierten Autorisierungsfilter, indem wir die AuthorizeAttribute-Klasse erben. Erstellen Sie eine neue leere MVC-Anwendung, fügen Sie wie gewohnt einen Infrastrukturordner hinzu und fügen Sie dann eine CustomAuthAttribute.cs-Klassendatei hinzu. Der Code lautet wie folgt:

namespace MvcApplication1.Infrastructure {
    public class CustomAuthAttribute : AuthorizeAttribute {
        private bool localAllowed;
        public CustomAuthAttribute(bool allowedParam) {
            localAllowed = allowedParam;
        }
        protected override bool AuthorizeCore(HttpContextBase httpContext) {
            if (httpContext.Request.IsLocal) {
                return localAllowed;
            }
            else {
                return true;
            }
        }
    }
}
Nach dem Login kopieren

Dieser einfache Filter ermöglicht durch Überschreiben der AuthorizeCore-Methode Wir können lokale Anfragen blockieren. Bei der Anwendung des Filters können wir angeben, ob lokale Anfragen über den Konstruktor zugelassen werden sollen. Die AuthorizeAttribte-Klasse hilft uns bei der Implementierung vieler integrierter Dinge. Wir müssen uns nur auf die AuthorizeCore-Methode konzentrieren und die Berechtigungsauthentifizierungslogik in dieser Methode implementieren.

Um die Funktion dieses Filters zu demonstrieren, erstellen wir einen neuen Controller mit dem Namen Home und wenden diesen Filter dann auf die Index-Aktionsmethode an. Der Parameter wird wie folgt auf „false“ gesetzt, um diese Aktion vor lokalem Zugriff zu schützen:

public class HomeController : Controller {

    [CustomAuth(false)]
    public string Index() {
        return "This is the Index action on the Home controller";
    }
}
Nach dem Login kopieren

Führen Sie das Programm aus und fordern Sie /Home/Index gemäß dem vom System generierten Standard-Routing-Wert an. Das Ergebnis ist wie folgt folgt:

[ASP.NET MVC Mavericks Road]11 – Filter

Wir haben einen einfachen Filter angepasst, indem wir die AuthorizeAttribute-Klasse als Basisklasse verwendet haben. Was sind also die nützlichen Funktionen der AuthorizeAttribute-Klasse selbst als Filter?

Verwenden Sie den integrierten Autorisierungsfilter

当我们直接使用 AuthorizeAttribute 类作为Filter时,可以通过两个属性来指定我们的权限策略。这两个属性及说明如下:

Users属性,string类型,指定允许访问action方法的用户名,多个用户名用逗号隔开。
Roles属性,string类型,用逗号分隔的角色名,访问action方法的用户必须属于这些角色之一。

使用如下:

public class HomeController : Controller {

    [Authorize(Users = "jim, steve, jack", Roles = "admin")]
    public string Index() {
        return "This is the Index action on the Home controller";
    }
}
Nach dem Login kopieren

这里我们为Index方法应用了Authorize特性,并同时指定了能访问该方法的用户和角色。要访问Index action,必须两者都满足条件,即用户名必须是 jim, steve, jack 中的一个,而且必须属性 admin 角色。

另外,如果不指定任何用户名和角色名(即 [Authorize] ),那么只要是登录用户都能访问该action方法。

你可以通过创建一个Internet模板的应用程序来看一下效果,这里就不演示了。

对于大部分应用程序,AuthorizeAttribute 特性类提供的权限策略是足够用的。如果你有特殊的需求,则可以通过继承AuthorizeAttribute 类来满足。


Exception Filter

Exception Filter,在下面三种来源抛出未处理的异常时运行:

另外一种Filter(如Authorization、Action或Result等Filter)。
Action方法本身。
Action方法执行完成(即处理ActionResult的时候)。

Exception Filter必须实现 IExceptionFilter 接口,该接口的定义如下:

namespace System.Web.Mvc { 
    public interface IExceptionFilter { 
        void OnException(ExceptionContext filterContext); 
    } 
    }
Nach dem Login kopieren

ExceptionContext 常用属性说明

在 IExceptionFilter 的接口定义中,唯一的 OnException 方法在未处理的异常引发时执行,其中参数的类型:ExceptionContext,它继承自 ControllerContext 类,ControllerContext 包含如下常用的属性:

Controller,返回当前请求的controller对象。
HttpContext,提供请求和响应的详细信息。
IsChildAction,如果是子action则返回true(稍后将简单介绍子action)。
RequestContext,提供请求上下文信息。
RouteData,当前请求的路由实例信息。

作为继承 ControllerContext 类的子类,ExceptionContext 类还提供了以下对处理异常的常用属性:

ActionDescriptor,提供action方法的详细信息。
Result,是一个 ActionResult 类型,通过把这个属性值设为非空可以让某个Filter的执行取消。
Exception,未处理异常信息。
ExceptionHandled,如果另外一个Filter把这个异常标记为已处理则返回true。

一个Exception Filter可以通过把 ExceptionHandled 属性设置为true来标注该异常已被处理过,这个属性一般在某个action方法上应用了多个Exception Filter时会用到。ExceptionHandled 属性设置为true后,就可以通过该属性的值来判断其它应用在同一个action方法Exception Filter是否已经处理了这个异常,以免同一个异常在不同的Filter中重复被处理。

示例演示

在 Infrastructure 文件夹下添加一个 RangeExceptionAttribute.cs 类文件,代码如下:

public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter {
    public void OnException(ExceptionContext filterContext) {
        if (!filterContext.ExceptionHandled && filterContext.Exception is ArgumentOutOfRangeException) {
            filterContext.Result = new RedirectResult("~/Content/RangeErrorPage.html");
            filterContext.ExceptionHandled = true;
        }
    }
}
Nach dem Login kopieren

这个Exception Filter通过重定向到Content目录下的一个静态html文件来显示友好的 ArgumentOutOfRangeException 异常信息。我们定义的 RangeExceptionAttribute 类继承了FilterAttribute类,并且实现了IException接口。作为一个MVC Filter,它的类必须实现IMvcFilter接口,你可以直接实现这个接口,但更简单的方法是继承 FilterAttribute 基类,该基类实现了一些必要的接口并提供了一些有用的基本特性,比如按照默认的顺序来处理Filter。

在Content文件夹下面添加一个名为RangeErrorPage.html的文件用来显示友好的错误信息。如下所示:

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
    <title>Range Error</title> 
</head> 
<body> 
    <h2>Sorry</h2> 
    <span>One of the arguments was out of the expected range.</span> 
</body> 
</html>
Nach dem Login kopieren

在HomeController中添加一个值越限时抛出异常的action,如下所示:

public class HomeController : Controller { 
        [RangeException]
        public string RangeTest(int id) { 
            if (id > 100) { 
                return String.Format("The id value is: {0}", id); 
            } else { 
                throw new ArgumentOutOfRangeException("id", id, ""); 
            } 
        } 
    }
Nach dem Login kopieren

当对RangeTest应用自定义的的Exception Filter时,运行程序URL请求为 /Home/RangeTest/50,程序抛出异常后将重定向到RangeErrorPage.html页面:

[ASP.NET MVC Mavericks Road]11 – Filter

由于静态的html文件是和后台脱离的,所以实际项目中更多的是用一个View来呈现友好的错误信息,以便很好的对它进行一些动态的控制。如下面把示例改动一下,RangeExceptionAttribute 类修改如下:

public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter {
        public void OnException(ExceptionContext filterContext) {
            if (!filterContext.ExceptionHandled && filterContext.Exception is ArgumentOutOfRangeException) {                int val = (int)(((ArgumentOutOfRangeException)filterContext.Exception).ActualValue);
                filterContext.Result = new ViewResult {
                    ViewName = "RangeError",
                    ViewData = new ViewDataDictionary<int>(val)
                };
                filterContext.ExceptionHandled = true;
            }
        }
    }
Nach dem Login kopieren

我们创建一个ViewResult对象,指定了发生异常时要重定向的View名称和传递的model对象。然后我们在Views/Shared文件夹下添加一个RangeError.cshtml文件,代码如下:

@model int

<!DOCTYPE html> 
<html> 
<head> 
    <meta name="viewport" content="width=device-width" /> 
    <title>Range Error</title> 
</head> 
<body> 
    <h2>Sorry</h2> 
    <span>The value @Model was out of the expected range.</span> 
    <div> 
        @Html.ActionLink("Change value and try again", "Index") 
    </div> 
</body> 
</html>
Nach dem Login kopieren

运行结果如下:

[ASP.NET MVC Mavericks Road]11 – Filter

禁用异常跟踪

很多时候异常是不可预料的,在每个Action方法或Controller上应用Exception Filter是不现实的。而且如果异常出现在View中也无法应用Filter。如RangeError.cshtml这个View加入下面代码:

@model int

@{ 
    var count = 0; 
    var number = Model / count; 
} 

...
Nach dem Login kopieren

运行程序后,将会显示如下信息:

[ASP.NET MVC Mavericks Road]11 – Filter

显然程序发布后不应该显示这些信息给用户看。我们可以通过配置Web.config让应用程序不管在何时何地引发了异常都可以显示统一的友好错误信息。在Web.config文件中的节点下添加如下子节点:

<system.web>
    
    ...
    <customErrors mode="On" defaultRedirect="/Content/RangeErrorPage.html"/>
  </system.web>
Nach dem Login kopieren

这个配置只对远程访问有效,本地运行站点依然会显示跟踪信息。

使用内置的 Exceptin Filter

通过上面的演示,我们理解了Exceptin Filter在MVC背后是如何运行的。但我们并不会经常去创建自己的Exceptin Filter,因为微软在MVC框架中内置的 HandleErrorAttribute(实现了IExceptionFilter接口) 已经足够我们平时使用。它包含ExceptionType、View和Master三个属性。当ExceptionType属性指定类型的异常被引发时,这个Filter将用View属性指定的View(使用默认的Layout或Mast属性指定的Layout)来呈现一个页面。如下面代码所示:

... 
[HandleError(ExceptionType = typeof(ArgumentOutOfRangeException), View = "RangeError")] 
public string RangeTest(int id) { 
    if (id > 100) { 
        return String.Format("The id value is: {0}", id); 
    } else { 
        throw new ArgumentOutOfRangeException("id", id, ""); 
    } 
} 
...
Nach dem Login kopieren

使用内置的HandleErrorAttribute,将异常信息呈现到View时,这个特性同时会传递一个HandleErrorInfo对象作为View的model。HandleErrorInfo类包含ActionName、ControllerName和Exception属性,如下面的 RangeError.cshtml 使用这个model来呈现信息:

@model HandleErrorInfo 
@{ 
    ViewBag.Title = "Sorry, there was a problem!"; 
} 
 
<!DOCTYPE html> 
<html> 
<head> 
    <meta name="viewport" content="width=device-width" /> 
    <title>Range Error</title> 
</head> 
<body> 
    <h2>Sorry</h2> 
    <span>The value @(((ArgumentOutOfRangeException)Model.Exception).ActualValue) 
        was out of the expected range.</span>         
    <div> 
        @Html.ActionLink("Change value and try again", "Index") 
    </div> 
    <div style="display: none"> 
        @Model.Exception.StackTrace 
    </div> 
</body> 
</html>
Nach dem Login kopieren

Action Filter

顾名思义,Action Filter是对action方法的执行进行“筛选”的,包括执行前和执行后。它需要实现 IActionFilter 接口,该接口定义如下:

namespace System.Web.Mvc { 
    public interface IActionFilter { 
        void OnActionExecuting(ActionExecutingContext filterContext); 
        void OnActionExecuted(ActionExecutedContext filterContext); 
    } 
}
Nach dem Login kopieren

其中,OnActionExecuting方法在action方法执行之前被调用,OnActionExecuted方法在action方法执行之后被调用。我们来看一个简单的例子。

在Infrastructure文件夹下添加一个ProfileActionAttribute类,代码如下:

using System.Diagnostics; 
using System.Web.Mvc; 

namespace MvcApplication1.Infrastructure { 
    public class ProfileActionAttribute : FilterAttribute, IActionFilter { 
        private Stopwatch timer; 
        public void OnActionExecuting(ActionExecutingContext filterContext) { 
            timer = Stopwatch.StartNew(); 
        } 
        public void OnActionExecuted(ActionExecutedContext filterContext) { 
            timer.Stop();
            if (filterContext.Exception == null) { 
                filterContext.HttpContext.Response.Write( 
                    string.Format("<div>Action method elapsed time: {0}</div>", timer.Elapsed.TotalSeconds)); 
            } 
        } 
    } 
}
Nach dem Login kopieren

在HomeController中添加一个Action并应用该Filter,如下:

... 
[ProfileAction] 
public string FilterTest() { 
    return "This is the ActionFilterTest action"; 
} 
...
Nach dem Login kopieren

运行程序,URL定位到/Home/FilterTest,结果如下:

[ASP.NET MVC Mavericks Road]11 – Filter

我们看到,ProfileAction的 OnActionExecuted 方法是在 FilterTest 方法返回结果之前执行的。确切的说,OnActionExecuted 方法是在action方法执行结束之后和处理action返回结果之前执行的。

OnActionExecuting方法和OnActionExecuted方法分别接受ActionExecutingContext和ActionExecutedContext对象参数,这两个参数包含了ActionDescriptor、Canceled、Exception等常用属性。


Result Filter

Result Filter用来处理action方法返回的结果。用法和Action Filter类似,它需要实现 IResultFilter 接口,定义如下:

namespace System.Web.Mvc { 
    public interface IResultFilter { 
        void OnResultExecuting(ResultExecutingContext filterContext); 
        void OnResultExecuted(ResultExecutedContext filterContext); 
    } 
}
Nach dem Login kopieren

IResultFilter 接口和之前的 IActionFilter 接口类似,要注意的是Result Filter是在Action Filter之后执行的。两者用法是一样的,不再多讲,直接给出示例代码。

在Infrastructure文件夹下再添加一个 ProfileResultAttribute.cs 类文件,代码如下:

public class ProfileResultAttribute : FilterAttribute, IResultFilter { 
    private Stopwatch timer; 
    public void OnResultExecuting(ResultExecutingContext filterContext) { 
        timer = Stopwatch.StartNew(); 
    } 
    public void OnResultExecuted(ResultExecutedContext filterContext) { 
        timer.Stop(); 
        filterContext.HttpContext.Response.Write( 
            string.Format("<div>Result elapsed time: {0}</div>",  timer.Elapsed.TotalSeconds)); 
    } 
}
Nach dem Login kopieren

应用该Filter:

... 
[ProfileAction] 
[ProfileResult] public string FilterTest() { 
    return "This is the ActionFilterTest action"; 
} 
...
Nach dem Login kopieren

内置的 Action 和 Result Filter

MVC框架内置了一个 ActionFilterAttribute 类用来创建action 和 result 筛选器,即可以控制action方法的执行也可以控制处理action方法返回结果。它是一个抽象类,定义如下:

public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter{ 
        public virtual void OnActionExecuting(ActionExecutingContext filterContext) { 
        } 
        public virtual void OnActionExecuted(ActionExecutedContext filterContext) { 
        } 
        public virtual void OnResultExecuting(ResultExecutingContext filterContext) { 
        } 
        public virtual void OnResultExecuted(ResultExecutedContext filterContext) { 
        } 
    } 
}
Nach dem Login kopieren

使用这个抽象类方便之处是你只需要实现需要加以处理的方法。其他和使用 IActionFilter 和 IResultFilter 接口没什么不同。下面是简单做个示例。

在Infrastructure文件夹下添加一个 ProfileAllAttribute.cs 类文件,代码如下:

public class ProfileAllAttribute : ActionFilterAttribute { 
    private Stopwatch timer; 
    public override void OnActionExecuting(ActionExecutingContext filterContext) { 
        timer = Stopwatch.StartNew(); 
    } 
    public override void OnResultExecuted(ResultExecutedContext filterContext) { 
        timer.Stop(); 
        filterContext.HttpContext.Response.Write(
        string.Format("<div>Total elapsed time: {0}</div>",  timer.Elapsed.TotalSeconds)); 
    } 
}
Nach dem Login kopieren

在HomeController中的FilterTest方法上应用该Filter:

... 
[ProfileAction] 
[ProfileResult] 
[ProfileAll] 
public string FilterTest() { 
    return "This is the FilterTest action"; 
} 
...
Nach dem Login kopieren

运行程序,URL定位到/Home/FilterTest,可以看到一个Action从执行之前到结果处理完毕总共花的时间:

[ASP.NET MVC Mavericks Road]11 – Filter

我们也可以Controller中直接重写 ActionFilterAttribute 抽象类中定义的四个方法,效果和使用Filter是一样的,例如:

public class HomeController : Controller { 
    private Stopwatch timer; 
    ...
    public string FilterTest() { 
        return "This is the FilterTest action"; 
    } 
    protected override void OnActionExecuting(ActionExecutingContext filterContext) { 
        timer = Stopwatch.StartNew(); 
    } 
    protected override void OnResultExecuted(ResultExecutedContext filterContext) { 
        timer.Stop(); 
        filterContext.HttpContext.Response.Write( 
            string.Format("<div>Total elapsed time: {0}</div>", 
            timer.Elapsed.TotalSeconds)); 
    }
Nach dem Login kopieren

注册为全局 Filter

全局Filter对整个应用程序的所有controller下的所有action方法有效。在App_Start/FilterConfig.cs文件中的RegisterGlobalFilters方法,可以把一个Filter类注册为全局,如:

using System.Web; 
using System.Web.Mvc; 
using MvcApplication1.Infrastructure; 
 
namespace MvcApplication1 { 
    public class FilterConfig { 
        public static void RegisterGlobalFilters(GlobalFilterCollection filters) { 
            filters.Add(new HandleErrorAttribute()); 
            filters.Add(new ProfileAllAttribute()); 
        } 
    } 
}
Nach dem Login kopieren

我们增加了filters.Add(new ProfileAllAttribute())这行代码,其中的filters参数是一个GlobalFilterCollection类型的集合。为了验证 ProfileAllAttribute 应用到了所有action,我们另外新建一个controller并添加一个简单的action,如下:

public class CustomerController : Controller { 
        public string Index() { 
            return "This is the Customer controller"; 
        } 
}
Nach dem Login kopieren

运行程序,将URL定位到 /Customer ,结果如下:

[ASP.NET MVC Mavericks Road]11 – Filter


其它常用 Filter

MVC框架内置了很多Filter,常见的有RequireHttps、OutputCache、AsyncTimeout等等。下面例举几个常用的。

RequireHttps,强制使用HTTPS协议访问。它将浏览器的请求重定向到相同的controller和action,并加上 https:// 前缀。
OutputCache,将action方法的输出内容进行缓存。
AsyncTimeout/NoAsyncTimeout,用于异步Controller的超时设置。(异步Controller的内容请访问 xxxxxxxxxxxxxxxxxxxxxxxxxxx)
ChildActionOnlyAttribute,使用action方法仅能被Html.Action和Html.RenderAction方法访问。

这里我们选择 OutputCache 这个Filter来做个示例。新建一个 SelectiveCache controller,代码如下:

public class SelectiveCacheController : Controller {
    public ActionResult Index() { 
        Response.Write("Action method is running: " + DateTime.Now); 
        return View(); 
    } 

    [OutputCache(Duration = 30)] 
    public ActionResult ChildAction() { 
        Response.Write("Child action method is running: " + DateTime.Now); 
        return View(); 
    } 
}
Nach dem Login kopieren


这里的 ChildAction 应用了 OutputCache filter,这个action将在view内被调用,它的父action是Index。

现在我们分别创建两个View,一个是ChildAction.cshtml,代码如下:

@{ 
    Layout = null; 
} 
 
<h4>This is the child action view</h4>
Nach dem Login kopieren

另一个是它的Index.cshtml,代码如下:

@{ 
    ViewBag.Title = "Index"; 
} 
 
<h2>This is the main action view</h2> 
 
@Html.Action("ChildAction")
Nach dem Login kopieren

运行程序,将URL定位到 /SelectiveCache ,过几秒刷新一下,可看到如下结果:

[ASP.NET MVC Mavericks Road]11 – Filter

[ASP.NET MVC Mavericks Road]11 – Filter

 以上就是[ASP.NET MVC 小牛之路]11 - Filter的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!


Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage