2024年2月

从编程的角度来看,一个MVC应用是由一系列Controller类型构建而成的,所以对于一个代表应用模型的ApplicationModel对象来说,它的核心就是Controllers属性返回的一组ControllerModel对象,每个ControllerModel对象是应用模型针对Controller类型的描述。

一、ControllerModel
二、 实例演示:Controller模型的构建
三、实例演示:定制Controller模型

一、ControllerModel

描述Controller类型的ControllerModel具有如下定义。该类型的Application属性返回作为当前应用模型的ApplicationModel对象。它的Actions属性返回的ActionModel是对所有定义在当前Controller类型中的Action方法的描述。描述Controller类型属性的PropertyModel对象则存放在ControllerProperties属性中,由于PropertyModel和描述Action方法参数的ParameterModel对象承载的都是服务于模型绑定的元数据,所以我们会将这两个类型的介绍放在一起。ControllerModel类型的Selectors属性返回的一组SelectorModel对象是对应用在Controller级别上的Action选择器的描述,我们会在后续内容中对SelectorModel对象进行单独介绍。

public class ControllerModel : ICommonModel, IFilterModel, IApiExplorerModel
{
    public ApplicationModel 			Application { get; set; }

    public IList<ActionModel> 		        Actions { get; }
    public IList<PropertyModel> 		ControllerProperties { get; }
    public IList<SelectorModel> 		Selectors { get; }

    public IDictionary<object, object> 	Properties { get; }
    public IList<IFilterMetadata> 		Filters { get; }
    public ApiExplorerModel 			ApiExplorer { get; set; }

    public TypeInfo 				ControllerType { get; }
    public IReadOnlyList<object> 		Attributes { get; }
    public string 				ControllerName { get; set; }
    public string 				DisplayName { get; }
    public IDictionary<string, string> 	RouteValues { get; }

    MemberInfo ICommonModel.MemberInfo { get; }
    string ICommonModel.Name { get; }
}

ControllerModel类型同时实现了ICommonModel、IFilterModel和IApiExplorerModel接口。默认注册的DefaultApplicationModelProvider会对ControllerModel对象做如下的设置:ControllerType和MemberInfo属性会设置为当前Controller的类型,该类型名称去除“Controller”后缀的字符串会作为Name和ControllerName的属性值。通过标注的特性注册到Controller类型上的过滤器会被提取出来,对应的元数据会添加到Filters属性中。ApiExplorer属性返回的ApiExplorerModel对象由标注在Controller类型上实现了IApiDescriptionGroupNameProvider和IApiDescriptionVisibilityProvider接口的特性构建而成。

DefaultApplicationModelProvider还会将标注到Controller类型上的所有特性提取出来,并将它们添加到Attributes属性中。如果特性类型实现了IRouteTemplateProvider接口,它们将专门用来构建特性路由信息或者路由约束,所以它们会从此列表中移除。DisplayName属性返回的显示名称通过对类型名称作相应格式化生成。DefaultApplicationModelProvider还会提取标注在Controller类型上实现了IRouteValueProvider接口的特性,并利用对应的设置来填充RouteValues属性返回的路由参数。目前唯一实现了该接口的是如下这个用来设置Area名称的AreaAttribute特性,设置的路由参数名称为“area”。

public interface IRouteValueProvider
{
    string RouteKey { get; }
    string RouteValue { get; }
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=true, Inherited=true)]
public abstract class RouteValueAttribute : Attribute, IRouteValueProvider
{
    public string RouteKey { get; }
    public string RouteValue { get; }
    protected RouteValueAttribute(string routeKey, string routeValue);
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=false, Inherited=true)]
public class AreaAttribute : RouteValueAttribute
{
    public AreaAttribute(string areaName) : base("area", areaName)
    {}
}

二、 实例演示:Controller模型的构建

我们照例通过一个简单的实例来演示应用模型中用以描述Controller的元数据采用的默认构建规则。我们在前面演示程序中定义如下这个测试Controller类型FoobarController。如代码片段所示,FoobarController类型上标注了三个特性,分别是用来指定Area的AreaAttribute、过滤器特性FoobarAttribute和设置ApiExplorer的ApiExplorerSettingsAttribute。FoobarController类型中定义了两个属性(A和B)和两个Action方法(Foo和Bar)。

[Area("test")]
[Foobar]
[ApiExplorerSettings(GroupName = "test")]
public class FoobarController
{
    public string A { get; set; }
    public string B { get; set; }

    [HttpGet("foo")]
    public void Foo() => throw new NotImplementedException();
    [HttpGet("bar")]
    public void Bar() => throw new NotImplementedException();
}

为了在页面上呈现描述FoobarController类型的ControllerModel对象的相关信息,我们对定义在HomeControllere中的Action方法Index作了相应的修改。如下面的代码片段所示,我们利用在方法中注入的ApplicationModelProducer对象根据FoobarController类型创建一个ApplicationModel对象,并将包含在该对象中用来描述FoobarController的ControllerModel作为Model呈现成默认的View中。

public class HomeController: Controller
{
    [HttpGet("/")]
    public IActionResult Index([FromServices]ApplicationModelProducer producer)
    {
        var applicationModel = producer.Create(typeof(FoobarController));
        return View(applicationModel.Controllers.Single());
    }
}

我们最后对Action方法Index对应的View文件作相应的修改。如下面的代码片段所示,这是一个Model类型为ControllerModel的强类型View,,它将ControllerModel承载的元数据呈现在一个表格中。

@using  Microsoft.AspNetCore.Mvc.ApplicationModels;
@model ControllerModel
@{
    var commonModel 	= (ICommonModel)Model;
    var actions 	= Model.Actions;
    var filters 	= Model.Filters;
    var properties 	= Model.ControllerProperties;
    var attributes 	= Model.Attributes;
    var routeValues 	= Model.RouteValues.ToArray();
}
<html>
<head>
    <title>Controller</title>
</head>
<body>
    <table border="1" cellpadding="0" cellspacing="0">
        <tr><td>ControllerType </td><td>@Model.ControllerType.Name</td></tr>
        <tr><td>ControllerName </td><td>@Model.ControllerName</td></tr>
        <tr><td>Name </td><td>@commonModel.Name</td></tr>
        <tr><td>DisplayName </td><td>@Model.DisplayName</td></tr>
        <tr><td rowspan="@actions.Count">Actions</td><td>@actions[0].ActionName</td></tr>
        @for (int index = 1; index < actions.Count; index++)
        {
            <tr><td>@actions[index].ActionName</td></tr>
        }
        <tr>
            <td rowspan="@filters.Count">Filters</td><td>@filters[0].GetType().Name</td>
        </tr>
        @for (int index = 1; index < filters.Count; index++)
        {
            <tr><td>@filters[index].GetType().Name</td></tr>
        }
        <tr>
            <td rowspan="@properties.Count">ControllerProperties</td>
            <td>@properties[0].PropertyName</td>
        </tr>
        @for (int index = 1; index < properties.Count; index++)
        {
            <tr><td>@properties[index].PropertyName</td></tr>
        }
        <tr>
            <td rowspan="@attributes.Count">Attributes</td>
            <td>@attributes[0].GetType().Name</td>
        </tr>
        @for (int index = 1; index < attributes.Count; index++)
        {
            <tr><td>@attributes[index].GetType().Name</td></tr>
        }
        <tr>
            <td rowspan="@routeValues.Length">RouteVlues</td>
            <td>@routeValues[0].Key = @routeValues[0].Value</td>
        </tr>
        @for (int index = 1; index < routeValues.Length; index++)
        {
            <tr><td>@routeValues[index].Key = @routeValues[index].Value</td></tr>
        }
        <tr>
            <td rowspan="2">ApiExplorer</td>
            <td>IsVisible = @Model.ApiExplorer.IsVisible </td>
        </tr>
        <tr><td>GroupName = @Model.ApiExplorer.GroupName </td></tr>
    </table>
</body>
</html>

改动后的演示程序启动后,我们利用浏览器访问应用的主页,可以得到如图1所示的输出结果。正如上面我们所说的,去除“Controller”字符后缀的类型名称成为了ControllerModel对象的Name和ControllerName的属性值(“Foobar”)。两个属性(A和B)和Action方法(Foo和Bar)转换成相应的PropertyModel和ActionModel对象并分别添加到ControllerModel对象的ControllerProperties和Actions属性中。通过标注特性注册的过滤器(FoobarAttribute)被添加到ControllerModel对象的Filters属性中。通过标注的AreaAttribute设置的Area名称最终转移到ControllerModel对象对象RouteValues属性中。ControllerModel对象的ApiExplorer属性返回的ApiExplorerModel对象很明显是通过标注在类型上的ApiExplorerSettingsAttribute特性创建的,而它的Attributes属性中包含了我们标注的三个特性。

clip_image002

图1Controller模型默认的构建规则

三、实例演示:定制Controller模型

通过前面介绍的针对应用模型的总体设计,我们知道针对Controller模型的定制可以通过自定义的IControllerModelConvention实现类型来实现,我们接下来利用这种方式来改变Controller默认的命名规则。我们在上面演示的程序中定义了如下这个ControllerNameAttribute特性,该特性类型实现了IControllerModelConvention接口,在实现的Apply方法中,我们将构造函数中设置的Controller名称应用到提供的ControllerModel对象上。我们将该特性标注到FoobarController类型上并将名称设置为“Baz”。

[AttributeUsage(AttributeTargets.Class)]
public class ControllerNameAttribute : Attribute, IControllerModelConvention
{
    public string ControllerName { get; }
    public ControllerNameAttribute(string name) => ControllerName = name;
    public void Apply(ControllerModel controller) => controller.ControllerName = ControllerName;
}

[Area("test")]
[Foobar]
[ApiExplorerSettings(GroupName = "test")]
[ControllerName("Baz")]
public class FoobarController
{
   …
}

改动后的演示程序启动后,我们利用浏览器访问应用的主页,可以得到如图2所示的输出结果。我们从图中可以看出,对于最终生成的用来描述FoobarController类型的ControllerModel对象来说,它的ControllerName属性被设置成我们指定的名称“Baz”,它的Name属性(ControllerModel类型针对ICommonModel接口的实现)返回的就是ControllerName属性值。

clip_image004

图2 自定义IControllerModelConvention实现类型定制Controller模型

ASP.NET Core MVC应用模型的构建[1]: 应用的蓝图
ASP.NET Core MVC应用模型的构建[2]: 应用模型
ASP.NET Core MVC应用模型的构建[3]: Controller模型
ASP.NET Core MVC应用模型的构建[4]: Action模型

本文介绍基于
Python

gdal
模块,对大量
多时相
栅格图像,批量绘制像元
时间序列折线图
的方法。

首先,明确一下本文需要实现的需求:现有三个文件夹,其中
第一个文件夹
存放了某一研究区域原始的多时相栅格遥感影像数据(每一景
遥感影像
对应一个
时相
,文件夹中有多景
遥感影像
),每一景遥感影像都是
.tif
格式;
第二个文件夹

第三个文件夹
则分别存放了
前述第一个文件夹中原始遥感影像
基于
2
种不同
滤波方法处理后
的遥感影像(同样是每一景
遥感影像
对应一个
时相
,文件夹中有多景
遥感影像
),每一景遥感影像同样也都是
.tif
格式。我们希望分别针对这三个文件夹中的多张遥感影像数据,随机绘制部分像元对应的时间序列曲线图(每一个
像元
对应一张
曲线图
,一张
曲线图
中有三条
曲线
);每一张曲线图的最终结果都是如下所示的类似的样式,
X
轴表示时间节点,
Y
轴就是具体的像素值。

知道了需求,我们便开始代码的书写。具体代码如下:

# -*- coding: utf-8 -*-
"""
Created on Wed Dec 14 00:48:48 2022

@author: fkxxgis
"""

import os
import numpy as np
import matplotlib.pyplot as plt
from osgeo import gdal

original_file_path = r"E:\AllYear\Original"
hants_file_path = r"E:\AllYear\Reconstruction"
sg_file_path = r"E:\AllYear\SG"
pic_file_path = r"E:\AllYear\Pic"
pic_num = 50
np.random.seed(6)

original_file_list = os.listdir(original_file_path)
tem_raster = gdal.Open(os.path.join(original_file_path, original_file_list[0]))
col_num = tem_raster.RasterXSize
row_num = tem_raster.RasterYSize
col_point_array = np.random.randint(0, col_num, pic_num)
row_point_array = np.random.randint(0, row_num, pic_num)
del tem_raster

hants_file_list = os.listdir(hants_file_path)
start_day = hants_file_list[0][12:15]
end_day = hants_file_list[-1][12:15]
day_list = [x for x in range(int(start_day), int(end_day) + 20, 10)]

for i in range(pic_num):
    original_pixel_list, hants_pixel_list, sg_pixel_list = [[] for x in range(3)]
    
    for tif in original_file_list:
        original_raster = gdal.Open(os.path.join(original_file_path, tif))
        original_array = original_raster.ReadAsArray()
        original_pixel_list.append(original_array[row_point_array[i],col_point_array[i]])
        
    for tif in hants_file_list:
        hants_raster = gdal.Open(os.path.join(hants_file_path, tif))
        hants_array = hants_raster.ReadAsArray()
        hants_pixel_list.append(hants_array[1, row_point_array[i],col_point_array[i]])
        
    sg_file_list = os.listdir(sg_file_path)
    for tif in sg_file_list:
        sg_raster = gdal.Open(os.path.join(sg_file_path, tif))
        sg_array = sg_raster.ReadAsArray()
        sg_pixel_list.append(sg_array[1, row_point_array[i],col_point_array[i]])
    
    pic_file_name = str(col_point_array[i]) + "_" + str(row_point_array[i]) + ".png"
    plt.figure(dpi = 300)
    plt.plot(original_pixel_list,color = "red", label = "Original")
    plt.plot(hants_pixel_list,color = "green", label = "HANTS")
    plt.plot(sg_pixel_list,color = "blue", label = "SG")
    plt.legend()
    plt.xticks(range(len(day_list)), day_list, fontsize = 11)
    plt.xticks(rotation = 45)
    plt.title(str(col_point_array[i]) + "_" + str(row_point_array[i]), fontweight = "bold")
    plt.savefig(os.path.join(pic_file_path, pic_file_name))
    plt.show()
    plt.clf()
    
    del original_raster
    del hants_raster
    del sg_raster

其中,
E:\AllYear\Original
为原始多时相遥感影像数据存放路径,也就是前述的
第一个文件夹
的路径;而
E:\AllYear\R

E:\AllYear\S
则是前述
第二个文件夹

第三个文件夹
对应的路径;
E:\AllYear\Pic
则是批量绘图后,图片保存的路径。这里请注意,在运行代码前我们需要在资源管理器中,将上述三个路径下的各文件以“
名称
”排序的方式进行排序(每一景遥感影像都是按照成像时间命名的)。此外,
pic_num
则是需要加以绘图的像元个数,也就表明后期我们所生成的曲线图的张数为
50

代码的整体思路也非常简单。首先,我们借助
os.listdir()
函数获取
original_file_path
路径下的所有栅格遥感影像文件,在基于
gdal.Open()
函数将这一文件下的第一景遥感影像打开后,获取其行数与列数;随后,通过
np.random.randint()
函数生成两个随机数数组,分别对应着后期我们绘图的像元的
行号

列号

在代码的下一部分(就是
hants_file_list
开头的这一部分),我们是通过截取文件夹中图像的名称,来确定后期我们生成的时间序列曲线图中
X
轴的标签(也就是每一个
x
对应的时间节点是什么)——其中,这里的
[12:15]
就表示对于我的栅格图像而言,其文件名的第
13

15
个字符表示了遥感影像的成像时间;大家在使用代码时依据自己的实际情况加以修改即可。在这里,我们得到的
day_list
,就是后期曲线图中
X
轴各个标签的内容。

随后,代码中最外层的
for
循环部分,即为批量绘图工作的开始。我们前面选择好了
50
个随机位置的像元,此时就可以遍历这些像元,对每一个像元在不同时相中的数值加以读取——通过
.ReadAsArray()
函数将栅格图像各波段的信息读取为
Array
格式,并通过对应的
行号

列号
加以像素值的获取;随后,将获取得到的像元在不同时相的数值通过
.append()
函数依次放入前面新生成的列表中。

在接下来,即可开始绘图的工作。其中,
pic_file_name
表示每一张曲线图的文件名称,这是通过当前像元对应的
行号

列号
来命名的;
plt.figure(dpi = 300)
表示设置绘图的
DPI

300
。随后,再对每一张曲线图的图名、图例与坐标轴标签等加以配置,并通过
plt.savefig()
函数将生成的图片保存在指定路径下。

最终,我们得到的多张曲线图结果如下图所示,其文件名通过
列号

行号
分别表示了当前这张图是基于哪一个像元绘制得到的;其中,每一张图的具体样式就是本文开头所展示的那一张图片的样子。

至此,大功告成。

中间件

Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

通俗的讲
:中间件就是匹配路由前和匹配路由完成后执行的一系列操作

路由中间件

Gin 中的中间件必须是一个 gin.HandlerFunc 类型,配置路由的时候可以传递多个 func 回调函数,最后一个 func 回调函数前面触发的方法都可以称为中间件

// 请求方式的源码参数,... 可以传入多个func(context *gin.Context)
// 可以在func(context *gin.Context)之前传入自定义的一些处理方法
(relativePath string, handlers ...HandlerFunc)
定义和使用路由中间件
写法一
// 定义一个中间件要执行的方法
func MiddlewareFunc() {
	fmt.Println("中间件方法")

}

func ApiRoutersInit(router *gin.Engine) {

	apiRouter := router.Group("api")
	{
		apiRouter.GET("list",
			func(context *gin.Context) {
				// 请求前执行中间件方法
				MiddlewareFunc()
			},
			// 执行请求
			func(context *gin.Context) {
				context.String(http.StatusOK, "ok")

			})

	}
}

写法二
// 定义一个中间件要执行的方法
func MiddlewareFunc(context *gin.Context) {
	fmt.Println("中间件方法")

}

func ApiRoutersInit(router *gin.Engine) {

	apiRouter := router.Group("api")
	{	
		// 写法二
		apiRouter.GET("list", MiddlewareFunc,func(context *gin.Context) {
				context.String(http.StatusOK, "ok")

			})

	}
}

ctx.Next()

中间件里面加上 ctx.Next()可以让我们在路由匹配完成后执行一些操作

func MiddlewareFunc(context *gin.Context) {
	
	fmt.Println("请求执行前")
  // 调用该请求的剩余处理程序
	context.Next() 
  // 执行后面的func(context *gin.Context)方法
  // 每调用一次	context.Next() ,向后执行一个func(context *gin.Context)
  
  // 执行完之后再执行打印
	fmt.Println("请求执行完成")


}
ctx.Abort

Abort 是终止的意思, ctx.Abort() 表示终止调用该请求的剩余处理程序

func MiddlewareFunc(context *gin.Context) {
	fmt.Println("aaa")
	// 终止该请求的剩余处理程序 
	context.Abort()
	fmt.Println("这里继续打印")
  
  


}

全局中间件

func main() {
	router := gin.Default()

	// 在匹配路由之前配置全局中间件

	// 使用Use配置全局中间件,参数就是中间件方法,可以传入多个,
	router.Use(MiddlewareFunc1,MiddlewareFunc2)

	router.GET("/", func(context *gin.Context) {
		context.String(http.StatusOK, "ok")

	})

	// 将默认引擎传给其他文件定义的接收引擎的方法
	api.ApiRoutersInit(router)
	router.Run()

}

路由分组中间件

方法一
func ApiRoutersInit(router *gin.Engine) {
	// 在路由分组的Group后配置中间件
	apiRouter := router.Group("api",MiddlewareFunc)
	{
		apiRouter.GET("list",
			// 执行请求
			func(context *gin.Context) {
				context.String(http.StatusOK, "ok")

			})

	}
}

方法二
func ApiRoutersInit(router *gin.Engine) {
	apiRouter := router.Group("api")
	// 调用group对象 配置中间件
	apiRouter.Use(MiddlewareFunc)
	{
		apiRouter.GET("list",
			// 执行请求
			func(context *gin.Context) {
				context.String(http.StatusOK, "ok")

			})

	}
}

中间件和对应控制器共享数据

// 中间件

func MiddlewareFunc(context *gin.Context) {
	// 通过Set设置一个数据 k,v
	context.Set("name", "li")

}
// 控制器
func (a ApiController) ApiSetInfo(context *gin.Context) {
  // 通过.Get(key) 获取值,获取到的是一个any类型的值和是否异常的bool
	username, _ := context.Get("name")
	// 通过类型断言获取string类型的name
	name, _ := username.(string)
	context.String(http.StatusOK, name)
}

中间件注意事项

gin默认中间件

gin.Default()默认使用了 Logger 和 Recovery 中间件,其中:

• Logger 中间件将日志写入 gin.DefaultWriter,即使配置了 GIN_MODE=release。

• Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500 响应码。

如果不想使用上面两个默认的中间件,可以使用 gin.New()新建一个没有任何默认中间件的路由

中间件中使用协程

当在中间件或 handler 中启动新的 goroutine 时,
不能使用
原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())

func MiddlewareFunc(context *gin.Context) {
	c := context.Copy()

	go func() {
		fmt.Println("url是", c.Request.URL)
	}()

	go func() {
		fmt.Println("body是", c.Request.Body)
	}()

}
	// 不需要wait等待协程完成,因为主程序main.go会一直执行

model

如果我们的应用非常简单的话,我们可以在 Controller 里面处理常见的业务逻辑。但是如果我们有一个功能想在多个控制器、或者多个模板里面复用的话,那么我们就可以把公共的功能单独抽取出来作为一个模块(Model)。 Model 是逐步抽象的过程,一般我们会在 Model里面封装一些公共的方法让不同 Controller 使用,也可以在 Model 中实现和数据库的交互

自定义一个models包,把通用的一些功能抽离来封装好,供其他地方调用,简单说models的概念就是封装一些公共方法

/*

*/
package models

import "time"

// 将时间戳转换为日期
func UnixToTime(timestamp int) string {
	t := time.Unix(int64(timestamp), 0)
	return t.Format("2006-01-02 15:04:05")
}

前言:

在这篇 Taurus.MVC WebMVC 入门开发教程的第三篇文章中,

我们将重点介绍如何进行数据绑定操作,还会学习如何使用 ${属性名称} CMS 语法来绑定页面上的元素与 Model 中的属性。

步骤1:创建 Model

首先,我们需要创建一个 Model 类来存储数据。

在 Visual Studio 中,右键单击项目文件夹,选择「添加」-> 「新建项」。在弹出的对话框中,选择「类」,并命名为「User.cs」。

在 User.cs 类中,我们可以定义一些属性来表示用户信息,例如姓名、年龄等。

public classUser
{
public string Name { get; set; }public int Age { get; set; }
}

步骤2:更新控制器

接下来,我们需要更新控制器以支持数据绑定。

在 HomeController.cs 文件中,我们可以修改 Index 方法,创建一个 User 对象,并将其传递给视图。

public classHomeController : Taurus.Mvc.Controller
{
public voidIndex()
{
User user
= newUser
{
Name
= "Alice",
Age
= 25};

View.LoadData(user);
}
public voidAbout() { }public voidContact() { }
}

View.LoadData 可以加载对象类型很多,比如:实体类,数据行(MDataRow )、字典、哈希等。

步骤3:更新视图

现在,我们需要更新视图来实现数据绑定。在 Index.html 视图文件中,

我们可以使用 ${属性名称} CMS 语法来绑定页面上的元素与 Model 中的属性。

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>欢迎来到 Taurus.MVC WebMVC</title>
</head>
<body>
    <h1>用户信息</h1>
    <p>姓名:${Name}</p>
    <p>年龄:${Age}</p>
</body>
</html>

在上述代码中,我们使用通过使用 ${
Name
} 和 ${Age} 语法来绑定页面上的元素与 Model 中的属性。

步骤4:运行应用程序

最后,我们可以运行应用程序并查看页面的效果。

您将看到用户信息页面上显示了用户的姓名和年龄,这些信息是通过数据绑定从 Model 中获取的。

步骤5:使用 View.KeyValue 添加绑定值

除了直接使用Model,通过 View.LoadData 来加载值外,对于一些场景,如果不想使用 Model,

比如想在界面绑定一些值,但不想重新定义Model时,则可以使用 View.KeyValue.Add(key,value)来添加。

例如框架内部实现中就使用到它,添加了几个默认值,以下是框架内部源码的实现部分:

private voidLoadHtmlView()
{
if (!CancelLoadHtml)
{
_View
= ViewEngine.Create(HtmlFolderName, HtmlFileName);//这里ControllerName用原始大写,兼容Linux下大小写名称。 if (_View != null)
{
//追加几个全局标签变量 _View.KeyValue.Add("module", ModuleName.ToLower());
_View.KeyValue.Add(
"controller", ControllerName);
_View.KeyValue.Add(
"action", MethodName.ToLower());
_View.KeyValue.Add(
"para", Para.ToLower());
_View.KeyValue.Add(
"suffix", Path.GetExtension(Request.Url.LocalPath));
_View.KeyValue.Add(
"httphost", Request.Url.AbsoluteUri.Substring(0, Request.Url.AbsoluteUri.Length -Request.Url.PathAndQuery.Length));
}
}
}

View.KeyValue是一个Dictionary,你可以添加,也可以移除。

实际View.LoadData 是将 Model 批量加载到了 View.KeyValue 中,后续你也可以通过它来移除不想绑定的数据。

总结

在本篇教程中,我们学习了如何在 Taurus.MVC WebMVC 中进行数据绑定操作。

我们还学习了如何使用 ${属性名称} CMS 语法来绑定页面上的元素与 Model 中的属性。

通过这些步骤,我们成功实现了一个简单的数据绑定示例。

本系列的目录大纲为:

Taurus.MVC WebMVC 入门开发教程1:框架下载环境配置与运行

Taurus.MVC WebMVC 入门开发教程2:一个简单的页面呈现

Taurus.MVC WebMVC 入门开发教程3:数据绑定Model

Taurus.MVC WebMVC 入门开发教程4:数据列表绑定List<Model>

Taurus.MVC WebMVC 入门开发教程5:表单提交与数据验证

Taurus.MVC WebMVC 入门开发教程6:路由配置与路由映射

Taurus.MVC WebMVC 入门开发教程7:部分视图和页面片段

C#程序的全局异常处理,网上搜下资料都是一大堆,我这里最近也是独立做一个B/S结构的小项目, 后面又增加了需求用WPF实现相同的功能,这里将我所使用的全局异常处理方式做一个简短的总结分享。
Web API项目的全局异常处理
这种项目下,我们可以直接自定义一个全局异常的过滤器,用来处理全局异常。具体步骤就是在WEB API项目中,定义一个继承自AppExceptionFilterAttribute的类,这个类需要引入命名空间Microsoft.AspNetCore.Mvc.Core.dll,一般来说如果是WEB API或者说MVC的项目,这个会自动帮我们引入或者说VS智能提示帮助我们引入。我们自定义的类只需要重写OnException方法即可,当然现在都推荐使用其异步模式OnExceptionAsync方法,我这里自定义的异常处理类是AppExceptionFilterAttribute,具体代码如下。

自定义的全局异常处理类AppExceptionFilterAttribute
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebApi
{
    [AttributeUsage(AttributeTargets.Method|AttributeTargets.Class,AllowMultiple =false,Inherited =false)]
    public class AppExceptionFilterAttribute:ExceptionFilterAttribute
    {
        //private static ILog log = LogManager.GetLogger(typeof(AppExceptionFilter));//typeof放当前类
        public override void OnException(ExceptionContext actionExecutedContext)
        {
            ActionDescriptor actionDescriptor = actionExecutedContext.ActionDescriptor;
            if (actionDescriptor.EndpointMetadata.Any(m => m.GetType() == typeof(SkipFilterAttribute)))
            {
                //判断如果操作方法有SkipFilterAttribute特性,则跳过自定义的过滤器的执行
                base.OnException(actionExecutedContext);
                return;
            }

            Exception ex = actionExecutedContext.Exception;
            if (ex != null)
            {
                string ControllerName = actionDescriptor.RouteValues["controller"];
                string ActionName = actionDescriptor.RouteValues["action"];
                //log.Error(string.Format("Filter捕获到未处理异常:ControllerName={0},ActionName={1}, ExType={2},Source={3},Message={4},Exception={5}", ControllerName, ActionName, ex.GetType(), ex.Source, ex.Message, ex.ToString()));

                JsonResult jsonResult = new JsonResult($"全局Filter已进行错误处理。{ex.Message}");
                jsonResult.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;

                actionExecutedContext.Result = jsonResult;
            }
            actionExecutedContext.ExceptionHandled = true;
        }
    }
}

在上述代码中,只需要将AppExceptionFilterAttribute标记在方法的头上或者Controller的头上即可,如果在方法头上,当程序执行到此方法出现未处理异常时,会由AppExceptionFilterAttribute来处理。我们可以在AppExceptionFilterAttribute中集成日志组件,将错误的异常日记记录下来。如果在Controller的头上标记此特性,那么针对此Controller中定义的方法,在出现异常时都会进入到AppExceptionFilterAttribute来处理。如果想让某个方法不去使用此异常过滤器来处理。我们还可以加上一个跳过的处理操作,上面的代码中SkipFilterAttribute类就是用来做跳过自定义异常处理的功能,只需要定义个空的特性处理类即可,代码如下:

跳过自定义异常处理的特性类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebApi
{
    /// <summary>
    /// 跳过过滤器的操作,定义在方法头上,带有此标记的方法不会去处理过滤
    /// </summary>
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public class SkipFilterAttribute : Attribute
    {

    }
}

最后贴上一段在Controller中使用的代码示例,这个示例中我是在Controller的头上使用了特性类AppExceptionFilterAttribute,而在接口方法Get3DModelData的头上使用了SkipFilterAttribute特性做标记,因此Get3DModelData出现异常后进入AppExceptionFilterAttribute中检测到方法头上标记了SkipFilterAttribute,会跳过自定义异常处理。

Controller中使用自定义异常处理
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebApi.Controllers
{
    [AppExceptionFilter]
    [Route("[controller]/[action]")]
    [ApiController]
    public class Dicom3DController : Controller
    {
        [SkipFilterAttribute]
        [HttpPost]
        public IActionResult Get3DModelData([FromBody]Dicom3DRequest request)
        {
            Dicom3DServer server = new Dicom3DServer();
            D3Model model= server.Get3DData(request.StudyId, request.SeireisId,request.CurrentBatch);
            
            return Json(model);
        }

        public IActionResult GetUserInfo()
        {
            return Json("success");
        }
    }
}

WPF全局异常处理
WPF程序的全局异常处理就更简单了,我们创建的WPF的项目入口类都是继承自System.Windows.Application类,默认创建的项目的入口类名为APP,我们在这个类中定义一个构造函数,在其中绑定三个异常事件即可,具体为什么需要三个,直接可以看我代码的注释即可。

WPF的全局异常处理
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        public App()
        {
            //当应用程序引发但未处理异常时出现,UI线程的异常,无法捕获多线程异常
            Application.Current.DispatcherUnhandledException += Current_DispatcherUnhandledException;

            //当某个异常未被捕获时出现,Thread多线程和UI线程都可以捕获
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

            //未被观察到的Task多线程异常
            TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
        }

        private void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
        {
            e.Handled = true; //使用e.Handled能防止程序崩溃
            MessageBox.Show($"Current_DispatcherUnhandledException:" + e.Exception.Message);
        }
        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            MessageBox.Show($"CurrentDomain_UnhandledException:" + (e.ExceptionObject as Exception).Message);
        }
        private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
        {
            MessageBox.Show($"TaskScheduler_UnobservedTaskException:" + e.Exception.Message);
        }

    }
}