在这里我们正在使用OData WCF服务来创建我们的新API。为了完全实现我们的API,我们已经开始使用自定义函数来扩展服务,这些自定义函数允许我们触发无法通过常规方法OData公开的特定功能。扩展OData服务
一个示例是将Workspace实体切换到高级模式。这需要对数据进行大量的检查和混合,我们选择将其移至单独的功能。这是我们Api.svc服务的完整代码:
using System.Net;
using System.ServiceModel.Web;
namespace TenForce.Execution.Web
{
using System;
using System.Data.Services;
using System.Data.Services.Common;
using System.Security.Authentication;
using System.ServiceModel;
using System.Text;
using Microsoft.Data.Services.Toolkit;
using Api2;
using Api2.Implementation.Security;
using Api2.OData;
/// <summary>
/// <para>This class represents the entire OData WCF Service that handles incoming requests and processes the data needed
/// for those requests. The class inherits from the <see cref="ODataService<T>">ODataService</see> class in the toolkit to
/// implement the desired functionality.</para>
/// </summary>
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class Api : ODataService<Context>
{
#region Initialization & Authentication
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.UseVerboseErrors = true;
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
Factory.SetImplementation(typeof(Api2.Implementation.Api));
}
/// <summary>
/// <para>This function is called when a request needs to be processed by the OData API.</para>
/// <para>This function will look at the headers that are supplied to the request and try to extract the relevant
/// user credentials from these headers. Using those credentials, a login is attempted. If the login is successfull,
/// the request is processed. If the login fails, an AuthenticationException is raised instead.</para>
/// <para>The function will also add the required response headers to the service reply to indicate the success
/// or failure of the Authentication attempt.</para>
/// </summary>
/// <param name="args">The arguments needed to process the incoming request.</param>
/// <exception cref="AuthenticationException">Invalid username and/or password.</exception>
protected override void OnStartProcessingRequest(ProcessRequestArgs args)
{
#if DEBUG
Authenticator.Authenticate("secretlogin", string.Empty, Authenticator.ConstructDatabaseId(args.RequestUri.ToString()));
#else
bool authSuccess = Authenticate(args.OperationContext, args.RequestUri.ToString());
args.OperationContext.ResponseHeaders.Add(@"TenForce-RAuth", authSuccess ? @"OK" : @"DENIED");
if (!authSuccess) throw new AuthenticationException(@"Invalid username and/or password");
#endif
base.OnStartProcessingRequest(args);
}
/// <summary>
/// <para>Performs authentication based upon the data present in the custom headers supplied by the client.</para>
/// </summary>
/// <param name="context">The OperationContext for the request</param>
/// <param name="url">The URL for the request</param>
/// <returns>True if the Authentication succeeded; otherwise false.</returns>
private static bool Authenticate(DataServiceOperationContext context, string url)
{
// Check if the header is present
string header = context.RequestHeaders["TenForce-Auth"];
if (string.IsNullOrEmpty(header)) return false;
// Decode the header from the base64 encoding
header = Encoding.UTF8.GetString(Convert.FromBase64String(header));
// Split the header and try to authenticate.
string[] components = header.Split('|');
return (components.Length >= 2) && Authenticator.Authenticate(components[0], components[1], Authenticator.ConstructDatabaseId(url));
}
#endregion
#region Service Methods
/*
* All functions that are defined in this block, are special Service Methods on our API Service that become
* available on the web to be called by external parties. These functions do not belong in the REST specifications
* and are therefore placed here as public functions.
*
* Important to know is that these methods become case-sensitive in their signature as well as their parameters when
* beeing called from the web. Therefore we need to properly document these functions here so the generated document
* explains the correct usage of these functions.
*/
/// <summary>
/// <para>Switches the specified <see cref="Workspace">Workspace</see> into advanced mode, using the specified
/// Usergroup as the working <see cref="Usergroup">Usergroup</see> for the Workspace.</para>
/// <para>The method can be called using the following signature from the web:</para>
/// <para>http://applicationurl/api.svc/SwitchWorkspaceToAdvancedMode?workspaceId=x&usergroupId=y</para>
/// <para>Where x stands for the unique identifier of the <see cref="Workspace">Workspace</see> entity and y stands for the unique
/// identifier of the <see cref="Usergroup">Usergroup</see> entity.</para>
/// <para>This method can only be invoked by a HTTP GET operation and returns a server response 200 when properly executed.
/// If the request fails, the server will respond with a BadRequest error code.</para>
/// </summary>
/// <param name="workspaceId">The unique <see cref="Workspace">Workspace</see> entity identifier.</param>
/// <param name="usergroupId">The unique <see cref="Usergroup">Usergroup</see> entity identifier.</param>
[WebGet]
public void SwitchWorkspaceToAdvancedMode(int workspaceId, int usergroupId)
{
Api2.Objects.Workspace ws = Factory.CreateApi().Workspaces.Read(workspaceId);
Api2.Objects.Usergroup ug = Factory.CreateApi().UserGroups.Read(usergroupId);
if(!Factory.CreateApi().Workspaces.ConvertToAdvancedPrivilegeSetup(ws, ug))
throw new WebFaultException(HttpStatusCode.BadRequest);
}
#endregion
}
}
的代码是有点大,但basicly这个额外的功能做的是检查所提供的标题为每个请求和针对与提供的用户名的应用程序进行身份验证和密码,以确保只有有效的用户才能使用我们的OData服务。
该问题目前存在于我们在底部声明的新函数中。 API需要设置用于执行功能的usercontext。这通常是通过Authenticator类完成的。
通过调试器,我跟踪了一个请求,并检查Authenticator是否正在调用并且是。然而,SwitchWorkspaceToAdvancedMode功能被触发时,这种情况下丢失,它看起来没有人曾经登录
函数调用是这样的:
- 创建一个新的Api.svc实例
- 触发的OnStartProcessingRequest
- 触发的身份验证方法触发该
- SwitchWorkspaceToAdvancedMode方法
但是最后一个从API接收到错误,指出没有登录发生,也没有设置用户上下文。这意味着我们将当前线程主体设置为登录的线程。
从错误消息中,我得出结论:SwitchWorkspaceToAdvancedMode的实际请求正在另一个线程上运行,因此似乎没有登录曾经发生,因为这是从不同的线程完成的。
我在这个假设是否正确,如果是这样,我可以防止这种情况或解决它吗?