Расширение службы OData - PullRequest
       1

Расширение службы OData

0 голосов
/ 04 августа 2011

Здесь, на работе, мы работаем с OData WCF Service, чтобы создать наш новый API.Чтобы полностью реализовать наш API, мы начали расширять сервис с помощью пользовательских функций, которые позволяют нам запускать определенные функции, которые не могут быть предоставлены с помощью обычных средств OData.

Одним из примеров является переключение сущности рабочего пространства врасширенный режим.Это требует много проверок и смешения с данными, которые мы решили перенести в отдельную функцию.Вот полный код нашей службы 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
    }
}

Код немного велик, но в основном эта дополнительная функция проверяет предоставленные заголовки для каждого запроса и аутентифицируется в приложении с помощьюпредоставил имя пользователя и пароль, чтобы гарантировать, что только действительные пользователи могут работать с нашей службой OData.

В настоящее время существует проблема в новой функции, которую мы объявили внизу.API требует, чтобы пользовательский контекст был установлен для выполнения функциональности.Обычно это делается с помощью класса Authenticator.

С помощью отладчика я следовал запросу и проверял, вызывается ли Authenticator, и так ли это.Однако при срабатывании функции SwitchWorkspaceToAdvancedMode этот контекст теряется, и он выглядит так, как будто никто не вошел в систему.

Вызовы функций выглядят следующим образом:

  • Создайте новый экземпляр Api.svc
  • Инициировать OnStartProcessingRequest
  • Инициировать метод Authenticate Инициировать метод
  • SwitchWorkspaceToAdvancedMode

Но этот последний получает ошибку от API, утверждая, чтовход в систему не произошел, и пользовательский контекст не был установлен.Это означает, что мы устанавливаем текущего участника потока в поток, который вошел в систему.

Из сообщений об ошибках я заключаю, что запрос actall для SwitchWorkspaceToAdvancedMode выполняется в другом потоке, и поэтому кажется, чтологин никогда не происходил, потому что это делается из другого потока.

Прав ли я в этом предположении, и если да, могу ли я предотвратить это или обойти его?

1 Ответ

0 голосов
/ 04 августа 2011

Я решил эту проблему, добавив новый ServiceBehavior к DataService:

[ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.PerSession)]

Это решило очевидную проблему с потоками, которая у меня была

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...