处理WCF Rest服务中的404错误

6
我有一个运行在IIS 7.5上的WCF REST服务。当有人访问一个不存在的端点时(例如:http://localhost/rest.svc/DOESNOTEXISThttp://localhost/EXISTS),他们会看到一个带有404状态码的通用WCF灰蓝色错误页面。然而,我想返回类似于以下内容:
<service-response>
   <error>The url requested does not exist</error>
</service-response>

我尝试在IIS中配置自定义错误,但只有在请求非REST服务页面时才起作用(即http://localhost/DOESNOTEXIST)。

有人知道如何做到这一点吗?

编辑 在下面的答案后,我能够想出我需要创建一个实现BehaviorExtensionElement的WebHttpExceptionBehaviorElement类。

 public class WebHttpExceptionBehaviorElement : BehaviorExtensionElement
 {
    ///  
    /// Get the type of behavior to attach to the endpoint  
    ///  
    public override Type BehaviorType
    {
        get
        {
            return typeof(WebHttpExceptionBehavior);
        }
    }

    ///  
    /// Create the custom behavior  
    ///  
    protected override object CreateBehavior()
    {
        return new WebHttpExceptionBehavior();
    }  
 }

然后,我可以通过以下方式在我的web.config文件中引用它:

<extensions>
  <behaviorExtensions>
    <add name="customError" type="Service.WebHttpExceptionBehaviorElement, Service"/>
  </behaviorExtensions>
</extensions>

然后添加

<customError /> 

关于我的默认端点行为的问题。

谢谢,

Jeffrey Kevin Pry


你使用REST Starter kit还是WCF Web API? - Tomasz Jaskuλa
2个回答

6
首先,创建一个自定义行为,它继承WebHttpBehavior-在这里,您将删除默认的未处理调度操作处理程序,并附加自己的处理程序:
public class WebHttpBehaviorEx : WebHttpBehavior
{
    public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        base.ApplyDispatchBehavior(endpoint, endpointDispatcher);

        endpointDispatcher.DispatchRuntime.Operations.Remove(endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation);
        endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation = new DispatchOperation(endpointDispatcher.DispatchRuntime, "*", "*", "*");
        endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation.DeserializeRequest = false;
        endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation.SerializeReply = false;
        endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation.Invoker = new UnknownOperationInvoker();

    }
}

接下来,创建一个未知操作处理程序。这个类将处理未知操作请求并生成一个“消息”作为响应。我展示了如何创建一个纯文本消息。根据你的需求进行修改应该是相当简单的:

internal class UnknownOperationInvoker : IOperationInvoker
{
    public object[] AllocateInputs()
    {
        return new object[1];
    }


    private Message CreateTextMessage(string message)
    {
        Message result = Message.CreateMessage(MessageVersion.None, null, new HelpPageGenerator.TextBodyWriter(message));
        result.Properties["WebBodyFormatMessageProperty"] = new WebBodyFormatMessageProperty(WebContentFormat.Raw);
        WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
        return result;
    }

    public object Invoke(object instance, object[] inputs, out object[] outputs)
    {
        // Code HERE

                StringBuilder builder = new System.Text.StringBuilder();

                builder.Append("...");

                Message result = CreateTextMessage(builder.ToString());

                return result;
    }

    public System.IAsyncResult InvokeBegin(object instance, object[] inputs, System.AsyncCallback callback, object state)
    {
        throw new System.NotImplementedException();
    }

    public object InvokeEnd(object instance, out object[] outputs, System.IAsyncResult result)
    {
        throw new System.NotImplementedException();
    }

    public bool IsSynchronous
    {
        get { return true; }
    }
}

此时,您需要将新的行为与您的服务关联起来。

有几种方法可以实现这一点,如果您还不知道,请随时询问,我很乐意进一步解释。


1
在Invoke部分中,我应该给输入和输出分配什么? - Jeffrey Kevin Pry
我会根据您的评论更新答案,但简单来说,请看我在invoke方法中如何“return result;”?它返回了一种“Message”类型...这就是您想要的。此链接:http://bit.ly/AwBaK有关于使用web config添加新客户行为的描述。 - Steve
谢谢...但在调用方法中,您还有一个out object[] outputs作为参数。它不允许我返回空输出。我正在返回一条消息。我将等待您的实现回复以关联行为。再次感谢您的所有帮助! - Jeffrey Kevin Pry
我已经实现了这个功能,但是当我的服务主机被生成时,Description.Endpoints集合为空(count== 0),我该如何为不存在的端点添加行为?为什么端点不存在? - Nathan Tregillus
我找出了为什么端点不存在的原因。因为我正在使用WebServiceHost,所以我必须从我的自定义服务主机工厂中调用host.AddDefaultEndpoints();。 - Nathan Tregillus
显示剩余5条评论

1

我曾经遇到过类似的问题,而其他答案确实帮助了我最终的成功,但并不是最清晰的答案。以下是我解决这个问题的方法。

我的项目设置是一个作为svc托管在IIS中的WCF服务。由于持续集成,每次检入时我的程序集版本都会发生变化,因此我无法通过配置路线来添加行为。

为了克服这个障碍,我创建了一个自定义的ServiceHostFactory:

using System.ServiceModel;
using System.ServiceModel.Activation;

namespace your.namespace.here
{
    public class CustomServiceHostFactory : WebServiceHostFactory
    {
        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            ServiceHost host = base.CreateServiceHost(serviceType, baseAddresses);
            //note: these endpoints will not exist yet, if you are relying on the svc system to generate your endpoints for you
            // calling host.AddDefaultEndpoints provides you the endpoints you need to add the behavior we need.
            var endpoints = host.AddDefaultEndpoints();
            foreach (var endpoint in endpoints)
            {
                endpoint.Behaviors.Add(new WcfUnkownUriBehavior());
            }

            return host;
        }
    }
}

正如您在上面看到的,我们正在添加一个新的行为:WcfUnknownUriBehavior。这个新的自定义行为的唯一职责是替换UnknownDispatcher。下面是该实现:

using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
namespace your.namespace.here
{
    public class UnknownUriDispatcher : IOperationInvoker
    {
        public object[] AllocateInputs()
        {
            //no inputs are really going to come in,
            //but we want to provide an array anyways
            return new object[1]; 
        }

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            var responeObject = new YourResponseObject()
            {
                Message = "Invalid Uri",
                Code = "Error",
            };
            Message result = Message.CreateMessage(MessageVersion.None, null, responeObject);
            WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
            outputs = new object[1]{responeObject};
            return result;
        }

        public System.IAsyncResult InvokeBegin(object instance, object[] inputs, System.AsyncCallback callback, object state)
        {
            throw new System.NotImplementedException();
        }

        public object InvokeEnd(object instance, out object[] outputs, System.IAsyncResult result)
        {
            throw new System.NotImplementedException();
        }

        public bool IsSynchronous
        {
            get { return true; }
        }
    }
}

一旦您指定了这些对象,您现在可以在svc的“标记”中使用新工厂:

<%@ ServiceHost Language="C#" Debug="true" Service="your.service.namespace.here" CodeBehind="myservice.svc.cs"
                Factory="your.namespace.here.CustomServiceHostFactory" %>

就是这样了。只要您的对象“YourResponseObject”可以序列化,它的序列化表示形式将发送回客户端。


1
哈哈... 你发了一个我一年前的答案的变体并且还踩了我的回答?好的,随你便。 - Steve
抱歉,不好意思。我不是故意给你的回答点了踩。可能是我的手指太粗了。请原谅我的失误。 - Nathan Tregillus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接