如何在没有魔术字符串的情况下将位置标头设置为WCF 4.0 REST中另
考虑以下两个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",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.etLocationHeader,以便它将使用Global.asax和UriTemplates中定义的路由表来返回文档服务的GetDocument操作的Uri? 我正在使用普通的旧WCF 4.0(而不是WCF Web API). 解决方法
偶然的是,我找到了由JoséF.Romaniello编写的
an article,它展示了如何为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); } 相同的代码片段可用于从其他服务(例如DocumentService.Get)设置工作空间的uri [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)); } } ResourceLinker的默认构造函数需要进行一些更改以获取Web应用程序的基本uri,同时考虑到HTTPS可能会在负载均衡器处终止.这不属于这个答案. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |