近日,在做完善资料中手机号字段验证的时候碰到一个问题。我的手机号验证函数是在网站根目录下Controller为Account,Action为Login的下面。Remote验证的路径组合下来实际上是/account/login。 手机号的字段验证model是这样写的:
[System.Web.Mvc.Remote("CheckPhoneExists", "Account", ErrorMessage = "手机号已存在")]
[Display(Name = "手机号码")]
public string PhoneNumber { get; set; }
但是由于在区域Area下面创建了MM区域,在下面的完善资料页面需要对手机号进行验证。可是结果是却出现了CheckPhoneExists 404错误。在fiddler中检测发现CheckPhoneExists 的路径是/mm/account/login。这说明把这个验证文件默认算在MM区域下了。而实际上是在根目录下的/account/login下面。查看Remote的重载函数,知道其有一个AreaName的参数。因为是根目录,那想当然的把AreaName设置为空。设置后代码如下:
[System.Web.Mvc.Remote("CheckPhoneExists", "Account", "", ErrorMessage = "手机号已存在")]
[Display(Name = "手机号码")]
public string PhoneNumber { get; set; }
设置后测试,但是仍然是404,路径是/mm/account/login,还是在MM区域下。把空字符串设置为null,问题依旧。这怎么办,在百度上找答案,可是根本找不到。又试着对AreaName参数设置为"/"或"~",这两个参数一设置,页面直接报错,根本不成。
后来还是在stackoverflow上找到一个解决方法。
在stackoverflow这篇文章中,作者分析了RemoteAttribute属性包含areaname重载构造函数,这个函数的源代码如下:
public RemoteAttribute(string action, string controller, string areaName) : this()
{
if (string.IsNullOrWhiteSpace(action))
{
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "action");
}
if (string.IsNullOrWhiteSpace(controller))
{
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controller");
}
this.RouteData["controller"] = controller;
this.RouteData["action"] = action;
if (!string.IsNullOrWhiteSpace(areaName))
{
this.RouteData["area"] = areaName;
}
}
可以看到,对areaname参数的处理是当不为空字符且不为null时,设置了this.RouteData["area"]=areaName;而对设置为空字符串或null的情况下,并没有处理。而我们在使用时的理解为如果为空或null则表示是根目录下的控制器方法。得出的结论是可以构造一个自定义的Remote属性函数,在远程校验引用时引用这个自定义函数来解决此问题。自定义的函数如下:
public class MyRemoteAttribute : RemoteAttribute
{
public MyRemoteAttribute(string action, string controller, string area)
: base(action, controller, area)
{
this.RouteData["area"] = area;
}
}
引用代码如下:
[MyRemote("IsValidUserName", "Validation", "", ErrorMessage = "Invalid username")]
public string Username { get; set; }
毫不怀疑,这样处理能解决这个问题。也不得不赞叹这种查看源码找出问题所在是在常规方法失效的情况下解决问题的一种正确思路。由于stackoverflow解决此问题时查看了System.Web.Mvc下面的RemoteAttribute.cs文件的源码,我也感兴趣,看了下。我突然发现了另一重载构造函数:
public RemoteAttribute(string action, string controller, AreaReference areaReference) : this(action, controller)
{
if (areaReference == AreaReference.UseRoot)
{
this.RouteData["area"] = null;
}
}
这个引用后,我查了下AreaRefrence.UseRoot,解释是当使用AreaRefrence.UseRoot做参数时在根区域中查找控制器。这似乎符合我的要求,如果这个能用,那也不用自定义Remote属性了。于是我进行了改造,改造后的代码如下:
[System.Web.Mvc.Remote("CheckPhoneExists", "Account", System.Web.Mvc.AreaReference.UseRoot, ErrorMessage = "手机号已存在")]
[Display(Name = "手机号码")]
public string PhoneNumber { get; set; }
编译测试后,果然,是在根区域中查找控制器,可以使用。这样问题解决了