考虑以下两个 WCF 4.0 REST 服务:
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class WorkspaceService
{
[WebInvoke(UriTemplate = "{id}/documents/{name}", Method = "POST")]
public Document CreateWorkspaceDocument(Stream stream, string id, string name)
{
/* CreateDocument is omitted as it isn't relevant to the question */
Document response = CreateDocument(id, name, stream);
/* set the location header */
SetLocationHeader(response.Id);
}
private void SetLocationHeader(string id)
{
Uri uri = new Uri("https://example.com/documents/" + id);
WebOperationContext.Current.OutgoingResponse.SetStatusAsCreated(uri);
}
/* methods to delete, update etc */
}
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class DocumentService
{
[WebGet(UriTemplate = "{id}")]
public Document GetDocument(string id)
{
}
/* methods to delete, update etc */
}
本质上,当有人在工作区中创建文档时,Location 标题设置为文档的位置,这本质上与调用
DocumentService.GetDocument
相同。手术。我的 global.asax 如下所示:
public class Global : HttpApplication
{
private void Application_Start(object sender, EventArgs e)
{
RegisterRoutes();
}
private void RegisterRoutes()
{
var webServiceHostFactory = new WebServiceHostFactory();
RouteTable.Routes.Add(new ServiceRoute("workspaces", webServiceHostFactory, typeof (WorkspaceService)));
RouteTable.Routes.Add(new ServiceRoute("documents", webServiceHostFactory, typeof (DocumentService)));
/* other services */
}
}
WorkspaceService.SetLocationHeader
的实现因为它对路由的设置方式做了一些假设。如果我要改变DocumentService
的路线那么由此产生的 Uri 将是不正确的。如果我改变了 DocumentService.GetDocument
的 UriTemplate ,那么由此产生的 Uri 也将是不正确的。如果 WorkspaceService 和 DocumentService 合并为一个服务,我可以写
SetLocationHeader
如下:var itemTemplate = WebOperationContext.Current.GetUriTemplate("GetDocument");
var uri = itemTemplate.BindByPosition(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri, id);
WebOperationContext.Current.OutgoingResponse.SetStatusAsCreated(uri);
怎么写
WorkspaceService.SetLocationHeader
这样它将使用 Global.asax 和 UriTemplates 中定义的路由表来返回 DocumentService 的 GetDocument 操作的 Uri?我使用的是普通的旧 WCF 4.0(不是 WCF Web API)。
最佳答案
偶然发现了an article由 José F. Romaniello 编写,它展示了如何为 WCF Web API 执行此操作并对其进行了调整。源代码在答案的末尾。
假设我有四个服务,路由注册更改为使用 ServiceRoute 的子类,我们稍后在扫描路由表时使用它来“评估”。
using System;
using System.Web;
using System.Web.Routing;
public class Global : HttpApplication
{
private void Application_Start(object sender, EventArgs e)
{
RegisterRoutes();
}
private void RegisterRoutes()
{
RouteTable.Routes.Add(new ServiceRoute<Service1>("s1"));
RouteTable.Routes.Add(new ServiceRoute<Service2>("s2"));
RouteTable.Routes.Add(new ServiceRoute<Service3>("s3"));
RouteTable.Routes.Add(new ServiceRoute<Service4>("s4"));
}
}
WorkspaceService.SetLocationHeader
现在看起来如下:private void SetLocationHeader(string id)
{
ResourceLinker resourceLinker = new ResourceLinker();
Uri uri = resourceLinker.GetUri<WorkspaceService>(s => s.Get(id));
WebOperationContext.Current.OutgoingResponse.SetStatusAsCreated(uri);
}
相同的代码片段可用于从其他服务设置工作区的 uri,例如
DocumentService.Get
[WebGet("{id}")]
public Document Get(string id)
{
// can be static
ResourceLinker resourceLinker = new ResourceLinker();
DocumentEntity entity = _repository.FindById(id);
Document document = new Document();
document.Name = entity.Name;
// map other properties
document.Workspace.Name = entity.Workspace.Name;
document.Workspace.Uri = resourceLinker.GetUri<WorkspaceService>(s => s.Get("0"));
// map other properties
return document;
}
使用这种方法,没有魔法字符串,更改方法名称、服务名称、路由表前缀不太可能会破坏系统。
这是改编自 article 的实现:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Web.Routing;
public interface IServiceRoute
{
Type ServiceType
{
get;
}
string RoutePrefix
{
get;
set;
}
}
public class ServiceRoute<T> : ServiceRoute, IServiceRoute
{
public ServiceRoute(string routePrefix) : this(routePrefix, new WebServiceHostFactory())
{
}
public ServiceRoute(string routePrefix, ServiceHostFactoryBase serviceHostFactory)
: base(routePrefix, serviceHostFactory, typeof (T))
{
RoutePrefix = routePrefix;
ServiceType = typeof (T);
}
#region IServiceRoute Members
public string RoutePrefix
{
get;
set;
}
public Type ServiceType
{
get;
private set;
}
#endregion
}
public static class RouteTableExtensions
{
public static void AddService<T>(this RouteCollection routeCollection, string routePrefix)
{
routeCollection.Add(new ServiceRoute<T>(routePrefix));
}
public static string GetRoutePrefixForType<T>(this RouteCollection routeCollection)
{
var routeServiceType = routeCollection
.OfType<IServiceRoute>()
.FirstOrDefault(r => r.ServiceType == typeof (T));
if (routeServiceType != null)
{
return routeServiceType.RoutePrefix;
}
return null;
}
}
public interface IResourceLinker
{
Uri GetUri<T>(Expression<Action<T>> restMethod);
}
public class ResourceLinker : IResourceLinker
{
private readonly Uri _baseUri;
public ResourceLinker()
: this("http://localhost:53865")
{
}
public ResourceLinker(string baseUri)
{
_baseUri = new Uri(baseUri, UriKind.Absolute);
}
#region IResourceLinker Members
public Uri GetUri<T>(Expression<Action<T>> restMethod)
{
var methodCallExpression = (MethodCallExpression) restMethod.Body;
var uriTemplateForMethod = GetUriTemplateForMethod(methodCallExpression.Method);
var args = methodCallExpression.Method
.GetParameters()
.Where(p => uriTemplateForMethod.Contains("{" + p.Name + "}"))
.ToDictionary(p => p.Name, p => ValuateExpression(methodCallExpression, p));
var prefix = RouteTable.Routes.GetRoutePrefixForType<T>();
var newBaseUri = new Uri(_baseUri, prefix);
var uriMethod = new UriTemplate(uriTemplateForMethod, true);
return uriMethod.BindByName(newBaseUri, args);
}
#endregion
private static string ValuateExpression(MethodCallExpression methodCallExpression, ParameterInfo p)
{
var argument = methodCallExpression.Arguments[p.Position];
var constantExpression = argument as ConstantExpression;
if (constantExpression != null)
{
return constantExpression.Value.ToString();
}
//var memberExpression = (argument as MemberExpression);
var lambdaExpression = Expression.Lambda(argument, Enumerable.Empty<ParameterExpression>());
var result = lambdaExpression.Compile().DynamicInvoke().ToString();
return result;
}
private static string GetUriTemplateForMethod(MethodInfo method)
{
var webGet = method.GetCustomAttributes(true).OfType<WebGetAttribute>().FirstOrDefault();
if (webGet != null)
{
return webGet.UriTemplate ?? method.Name;
}
var webInvoke = method.GetCustomAttributes(true).OfType<WebInvokeAttribute>().FirstOrDefault();
if (webInvoke != null)
{
return webInvoke.UriTemplate ?? method.Name;
}
throw new InvalidOperationException(string.Format("The method {0} is not a web method.", method.Name));
}
}
考虑到 HTTPS 可能会在负载均衡器处终止,ResourceLinker 的默认构造函数需要进行一些更改以获取 Web 应用程序的基本 uri。这不属于这个答案。
关于wcf - 如何将位置 header 设置为 WCF 4.0 REST 中另一个服务的 UriTemplate 没有魔术字符串?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9317904/