您的位置:首页 > 其它

深入Atlas系列:Web Sevices Access in Atlas示例(6) - 在客户端隐藏服务器端类型信息

2016-07-29 00:00 369 查看
  如果要在客户端指定服务器端Web Service方法所接收的参数类型,就必须在客户端通过“__type”来指定,但是这就暴露了服务器端的具体类型了,这可不太好。现在我们就来看一下应该如何解决这个问题。在这里,我会使用以前的文章《深入Atlas系列:Web Sevices Access in Atlas示例(3) - 在Web Services方法中使用多态》里用过的例子,不过它的内容是使用CTP版本的Atlas,已经过期,因此还是需要一些改变。这个示例会分成好几步进行,我们一点点地来看它的实现:

1、定义需要的类型

首先,我们定义一下所需的类型。我们的目标是计算某种类型员工的工资,于是,我们先定义一个员工的抽象类:

namespace
Jeffz.HiddenTypes
{

public

abstract

class
Employee
{

private

int
_Years;

public

int
Years
{

get

{

return

this
._Years;
}

set

{

this
._Years
=
value;
}
}

public

string
RealStatus
{

get

{

return

this
.GetType().Name;
}
}

public

abstract

int
CalculateSalary();
}
}

然后定义一下可怜的实习生,不管干多少年,永远只有2000元工资:

namespace
Jeffz.HiddenTypes
{

public

class
Intern : Employee
{

public

override

int
CalculateSalary()
{

return

2000
;
}
}
}

然后是签第三方公司的合同工,底薪5000,每年增加1000:

namespace
Jeffz.HiddenTypes
{

public

class
Vendor : Employee
{

public

override

int
CalculateSalary()
{

return

5000

+

1000

*
(Years
-

1
);
}
}
}

最后是正式员工(全职工),底薪12000,每年增加2000:

namespace
Jeffz.HiddenTypes
{

public

class
FulltimeEmployee : Employee
{

public

override

int
CalculateSalary()
{

return

12000

+

2000

*
(Years
-

1
);
}
}
}

2、制作一个不隐藏服务器端类型的应用

首先,自然是定义一个Web Service,我们将其命名为ExposedRealTypesService.asmx

[WebService(Namespace
=

" http://tempuri.org/ "
)]
[WebServiceBinding(ConformsTo
=
WsiProfiles.BasicProfile1_1)]
[ScriptService]

public

class
ExposedRealTypesService : System.Web.Services.WebService
{
[GenerateScriptType(
typeof
(Intern))]
[GenerateScriptType(
typeof
(Vendor))]
[GenerateScriptType(
typeof
(FulltimeEmployee))]
[WebMethod]

public

string
CalculateSalary(Employee employee)
{

return

"
I'm
"

+
employee.RealStatus
+

"
, my salary is
"

+
employee.CalculateSalary()
+

"
.
"
;
}

}

在这里我们使用了GenerateScriptTypeAttribute来告诉这个Web Service:“我们可能会使用这些类来作为参数传递给你,请注意JSON字符串里的__type标志”,于是我们就能使用了。我们来看一下我们需要的HTML:

<
asp:ScriptManager
ID
="ScriptManager"
runat
="server"
>

<
Services
>

<
asp:ServiceReference
Path
="ExposedRealTypesService.asmx"
InlineScript
="false"

/>

</
Services
>

</
asp:ScriptManager
>

<
div
>
Years:
<
input
type
="text"
id
="txtYears"

/></
div
>

<
div
>

Status:

<
select
id
="comboStatus"
style
="width:150px;"
>

<
option
value
="Jeffz.HiddenTypes.Intern"
>
Intern
</
option
>

<
option
value
="Jeffz.HiddenTypes.Vendor"
>
Vendor
</
option
>

<
option
value
="Jeffz.HiddenTypes.FulltimeEmployee"
>
FTE
</
option
>

</
select
>

</
div
>

<
input
type
="button"
onclick
="calculateSalary()"
value
="Calculate!"

/>

<
h1
>
Result:
</
h1
>

<
div
id
="result"
></
div
>

所需的JavaScript代码如下:

function
calculateSalary()
{

var
emp
=
eval(
"
new
"

+
$get(
"
comboStatus
"
).value
+

"
()
"
);
emp.Years
=
parseInt($get(
"
txtYears
"
).value,
10
);

ExposedRealTypesService.CalculateSalary(emp, onComplete);
}

function
onComplete(result)
{
$get(
"
result
"
).innerHTML
=
result;
}

由于comboStatue的value就是客户端的类名,因此我使用了拼接字符串并且eval的方法生成客户端的类,并作为参数传递过去。其余的代码应该非常简单,我们就来看一下使用效果吧:

打开页面,先选择Intern,输入工龄为2,点击“Calculate!”按钮:



再选择FTE,输入工龄为5,点击“Calculate!”按钮:



嘿,这说明我们客户端传了不同的服务器对象给Web Service方法了。

3、隐藏客户端的__type信息

我们打开Fiddler看看请求的内容吧:



嗯?看到了JSON字符串里的信息了不?明明白白地写着“Jeffz.HiddenTypes.Intern”!哎,怎么能把我们服务器端的类型信息给暴露出去呢?这样是不是太危险了一点?不过没有关系,我们可以这么做。首先,修改一下Web Service方法,我们这里就另存为HiddenRealTypesService.asmx吧:

[WebService(Namespace = "http://tempuri.org/")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

[ScriptService]

public class HiddenRealTypesService : System.Web.Services.WebService

{

[GenerateScriptType(typeof(Intern),
ScriptTypeId="Intern")]

[GenerateScriptType(typeof(Vendor),
ScriptTypeId = "Vendor")]

[GenerateScriptType(typeof(FulltimeEmployee),
ScriptTypeId = "FTE")]

[WebMethod]

public string CalculateSalary(Employee employee)

{

return "I'm " + employee.RealStatus + ", my salary is " + employee.CalculateSalary() + ".";

}

}

注意到了在使用GenerateScriptTypeAttribute时我们改变了什么吗?对了,就是我们设置了“ScriptTypeId”的值。这是什么?我们来看一下GenerateScriptTypeAttribute的使用方式吧。如下:

internal

class
WebServiceData : JavaScriptTypeResolver
{
……

private

void
ProcessIncludeAttributes(GenerateScriptTypeAttribute[] attributes)
{

foreach
(GenerateScriptTypeAttribute attribute1
in
attributes)
{

if
(
!
string
.IsNullOrEmpty(attribute1.ScriptTypeId))
{

this
._typeResolverSpecials[attribute1.Type.FullName]
=
attribute1.ScriptTypeId;
}

……

this
.ProcessClientType(type1);
}
}

……
}

这段代码就是ASP.NET AJAX用于处理Web Service的WebServiceData类,当然它也提供了许多功能。在ProcessIncludeAttributes方法中会处理所有的GenrateScriptTypeAttribute,可以看到在这里为每个Attribute的ScriptTypeId与Type的FullName进行了映射。这种映射是不是让你想到了JavaScriptTypeResovler?没错,WebServiceData类就是继承了JavaScriptTypeResolver,它辅助了ASP.NET AJAX客户端访问Web Service方法时的序列化与反序列化工作。关于这一点,在我之前的文章《深入Atlas系列:探究序列化与反序列化能力(上) - 客户端支持,JavaScriptTypeResolver与JavaScriptConverter》里有比较详细的描述。

我们来看一下效果,当然在这之前还需要修改一下页面中ScriptManager的Service引用,如下:

<
asp:ScriptManager
ID
="ScriptManager"
runat
="server"
>

<
Services
>

<
asp:ServiceReference
Path
="HiddenRealTypesService.asmx"

/>

</
Services
>

</
asp:ScriptManager
>

再用Fiddler看一下传输内容吧,如下:



嘿,这样就不会把服务器端的具体类型暴露给别人了,不是吗?嗯,我们再多想想……:)

4、隐藏客户端代码调用时使用的具体信息

我们还有相当的路要走。有没有发现,我们在调用中带有了非常“明显”的类型信息。我们的方式其实和下面的差不多:

var
emp
=

new
Jeffz.HiddenTypes.Intern();
……
HiddenRealTypesService.CalculateSalary(emp, onComplete);

“Jeffz.HiddenTypes.Intern”?这不还是服务器端的具体类型吗?那么该怎么办呢?其实“new Jeffz.HiddenTypes.Intern()”操作也只是返回了一个普通的Object对象,不过它带有“__type”这个信息,其值为服务器端具体类型的ID。因此我们其实只要像下面这么做,也能得到同样的效果了。

首先,将<select />元素改成如下的形式:

<
select
id
="comboStatus"
style
="width:150px;"
>

<
option
value
="Intern"
>
Intern
</
option
>

<
option
value
="Vendor"
>
Vendor
</
option
>

<
option
value
="FTE"
>
FTE
</
option
>

</
select
>

这样就不会暴露出服务器端的具体类型了。然后我们相应地修改JavaScript代码,如下:

function
calculateSalary()
{

var
emp
=
{ '__type' : $get(
"comboStatus"
).value };
emp.Years
=
parseInt($get(
"
txtYears
"
).value,
10
);

HiddenRealTypesService.CalculateSalary(emp, onComplete);
}

再使用一下代码,一切正常,我们需要知道的只是服务器端具体类型的ID。这下完美了吧!再等等,再想想……

5、取消使用Service代理

我们还使用了ASP.NET AJAX为Web Service生成的代理,我们服务器端具体类型的信息都在那里,这不行,赶快取消!当然,在取消的同时不能破坏了Web Service方法的正常使用。我们该怎么做呢?

事实上,Web Service代理是通过访问XXXXX.asmx/js(Release模式)或者XXXXX.asmx/jsdebug输出的。我们必须禁止它。在这里我选择的方式是在Global.asax提供Application_BeginRequest的定义,它会在请求的一开始被调用。代码如下:

void
Application_BeginRequest(
object
sender, EventArgs e)
{

string
pathInfo
=

this
.Context.Request.PathInfo;

if
(
"
/js
"
.Equals(pathInfo, StringComparison.OrdinalIgnoreCase)
||

"
/jsdebug
"
.Equals(pathInfo, StringComparison.OrdinalIgnoreCase))
{

throw

new
InvalidOperationException(
"
You can't get the proxy script!
"
);
}
}

我们判断了请求地址的PathInfo,如果为“/js”或者“/jsdebug”时,则抛出异常,这样就禁止获得了任何的Web Service的Proxy信息,当然您也可以有选择地禁掉部分而不是全部。

哎,这样我们不就不能使用Web Service的Proxy了吗?没错,所以我们还必须修改一下JavaScript代码,使用一种相对“原始”的方式访问Web Service方法。如下:

var
proxy
=
{
_get_path :
function
(){
return

"
HiddenRealTypesService.asmx
"
},
get_defaultFailedCallback :
function
(){},
get_defaultUserContext :
function
(){},
get_timeout :
function
(){
return

0
;
/*
unlimited
*/
}
};

function
calculateSalary()
{

var
emp
=
{ '__type' : $get(
"
comboStatus
"
).value };
emp.Years
=
parseInt($get(
"
txtYears
"
).value,
10
);

Sys.Net._WebMethod._invoke(
proxy,
//
proxy

"
CalculateSalary
"
,
//
method name for calling

"
CalculateSalary
"
,
//
method name for showing

false
,
//
do not use HTTP GET

{ employee : emp },
//
parameters

onComplete
//
onSuccess

);
}

这里定义了一个Proxy,这是使用Sys.Net._WebMethod所必须的。上面代码的原理,在我以前的文章《深入Atlas系列:Web Sevices Access in Atlas(7) - RTM中的客户端支持》有比较详细的分析。

到现在,我们终于完全隐藏了服务器端的类型信息,不过这是靠编写更多的代码而换来的(不过似乎还算好)。孰优孰劣,孰轻孰重,就只能请大家自己判断了。:)

点击这里下载代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: