سؤال

لدي خدمة WCF التي لديها Thread.CurrentPrincipal المنصوص عليها في ServiceConfiguration.ClaimsAuthorizationManager.

عندما أقوم بتنفيذ الخدمة بشكل غير متزامن مثل هذا:

    public IAsyncResult BeginMethod1(AsyncCallback callback, object state)
    {
        // Audit log call (uses Thread.CurrentPrincipal)

        var task = Task<int>.Factory.StartNew(this.WorkerFunction, state);

        return task.ContinueWith(res => callback(task));
    }

    public string EndMethod1(IAsyncResult ar)
    {
        // Audit log result (uses Thread.CurrentPrincipal)

        return ar.AsyncState as string;
    }

    private int WorkerFunction(object state)
    {
        // perform work
    }

أجد أنه تم تعيين Thread.CurrentPrincipal على ClaimsPrincipal الصحيح في طريقة البداية وأيضًا في WorkerFunction، ولكن في طريقة النهاية تم تعيينه على GenericPrincipal.

أعلم أنه يمكنني تمكين توافق ASP.NET للخدمة والاستخدام HttpContext.Current.User الذي لديه المبدأ الصحيح في جميع الأساليب، لكنني أفضل عدم القيام بذلك.

هل هناك طريقة لإجبار Thread.CurrentPrincipal على ClaimsPrincipal الصحيح دون تشغيل توافق ASP.NET؟

هل كانت مفيدة؟

المحلول

بدءا من أ ملخص لنقاط تمديد WCF, ، ستشاهد الخيار المصمم خصيصًا لحل مشكلتك.ويسمى أ CallContextInitializer.تفقد هذا المقالة التي تعطي نموذج التعليمات البرمجية لـ CallContextInitializer.

إذا قمت بإنشاء ملحق ICallContextInitializer، فسيتم منحك التحكم في سياق مؤشر الترابط BeginXXX و سياق مؤشر ترابط EndXXX.أنت تقول أن ClaimsAuthorizationManager قام بإنشاء مدير المستخدم بشكل صحيح في طريقة BeginXXX(...) الخاصة بك.في هذه الحالة، يمكنك بعد ذلك إنشاء ICallContextInitializer مخصص لك والذي يقوم إما بتعيين أو تسجيل CurrentPrincipal، اعتمادًا على ما إذا كان يتعامل مع BeginXXX() أو EndXXX().شيء مثل:

public object BeforeInvoke(System.ServiceModel.InstanceContext instanceContext, System.ServiceModel.IClientChannel channel, System.ServiceModel.Channels.Message request){
    object principal = null;
    if (request.Properties.TryGetValue("userPrincipal", out principal))
    {
        //If we got here, it means we're about to call the EndXXX(...) method.
        Thread.CurrentPrincipal = (IPrincipal)principal;
    }
    else
    {
        //If we got here, it means we're about to call the BeginXXX(...) method.
        request.Properties["userPrincipal"] = Thread.CurrentPrincipal;            
    }
    ...
 }

لمزيد من التوضيح، النظر في حالتين.لنفترض أنك قمت بتطبيق كل من ICallContextInitializer وIParameterInspector.لنفترض أنه من المتوقع تنفيذ هذه الخطافات باستخدام خدمة WCF متزامنة ومع خدمة WCF غير متزامنة (وهي حالتك الخاصة).

وفيما يلي تسلسل الأحداث وشرح ما يحدث:

حالة متزامنة

ICallContextInitializer.BeforeInvoke();
IParemeterInspector.BeforeCall();
//...service executes...
IParameterInspector.AfterCall();
ICallContextInitializer.AfterInvoke();

لا يوجد شيء مفاجئ في الكود أعلاه.لكن انظر الآن أدناه إلى ما يحدث مع عمليات الخدمة غير المتزامنة...

حالة غير متزامنة

ICallContextInitializer.BeforeInvoke();  //TryGetValue() fails, so this records the UserPrincipal.
IParameterInspector.BeforeCall();
//...Your BeginXXX() routine now executes...
ICallContextInitializer.AfterInvoke();

//...Now your Task async code executes (or finishes executing)...

ICallContextInitializercut.BeforeInvoke();  //TryGetValue succeeds, so this assigns the UserPrincipal.
//...Your EndXXX() routine now executes...
IParameterInspector.AfterCall();
ICallContextInitializer.AfterInvoke();

كما ترون، يضمن CallContextInitializer أن لديك فرصة لتهيئة قيم مثل CurrentPrincipal قبل تشغيل روتين EndXXX() مباشرةً.لذلك لا يهم أن يتم تنفيذ روتين EndXXX() بالتأكيد على مؤشر ترابط مختلف عن روتين BeginXXX().و نعم ال System.ServiceModel.Channels.Message الكائن الذي يقوم بتخزين مبدأ المستخدم الخاص بك بين أساليب البداية/النهاية، يتم الاحتفاظ به ونقله بشكل صحيح بواسطة WCF على الرغم من تغيير مؤشر الترابط.

بشكل عام، يسمح هذا الأسلوب لـ EndXXX(IAsyncresult) الخاص بك بالتنفيذ باستخدام IPrincipal الصحيح، دون الحاجة إلى إعادة إنشاء CurrentPrincipal بشكل صريح في روتين EndXXX().وكما هو الحال مع أي سلوك WCF، يمكنك تحديد ما إذا كان هذا ينطبق على العمليات الفردية، أو جميع العمليات على العقد، أو جميع العمليات على نقطة النهاية.

نصائح أخرى

ليست الإجابة على سؤالي حقًا، ولكنها طريقة بديلة لتنفيذ خدمة WCF (في .NET 4.5) والتي لا تظهر نفس المشكلات مع Thread.CurrentPrincipal.

    public async Task<string> Method1()
    {
        // Audit log call (uses Thread.CurrentPrincipal)

        try
        {
            return await Task.Factory.StartNew(() => this.WorkerFunction());
        }
        finally 
        {
            // Audit log result (uses Thread.CurrentPrincipal)
        }
    }

    private string WorkerFunction()
    {
        // perform work
        return string.Empty;
    }

الطريقة الصحيحة لذلك هي إنشاء امتداد:

public class SLOperationContext : IExtension<OperationContext>
{
    private readonly IDictionary<string, object> items;

    private static ReaderWriterLockSlim _instanceLock = new ReaderWriterLockSlim();

    private SLOperationContext()
    {
        items = new Dictionary<string, object>();
    }

    public IDictionary<string, object> Items
    {
        get { return items; }
    }

    public static SLOperationContext Current
    {
        get
        {
            SLOperationContext context = OperationContext.Current.Extensions.Find<SLOperationContext>();
            if (context == null)
            {
                _instanceLock.EnterWriteLock();
                context = new SLOperationContext();
                OperationContext.Current.Extensions.Add(context);
                _instanceLock.ExitWriteLock();
            }
            return context;
        }
    }

    public void Attach(OperationContext owner) { }
    public void Detach(OperationContext owner) { }
}

يتم الآن استخدام هذا الامتداد كحاوية للكائنات التي تريد استمرارها بين تبديل الخيط حيث سيظل OperationContext.Current كما هو.

يمكنك الآن استخدام هذا في BeginMethod1 لحفظ المستخدم الحالي:

SLOperationContext.Current.Items["Principal"] = OperationContext.Current.ClaimsPrincipal;

وبعد ذلك في EndMethod1 يمكنك الحصول على المستخدم عن طريق كتابة:

ClaimsPrincipal principal = SLOperationContext.Current.Items["Principal"];

تحرير (نهج آخر):

public IAsyncResult BeginMethod1(AsyncCallback callback, object state)
{
    var task = Task.Factory.StartNew(this.WorkerFunction, state);

    var ec = ExecutionContext.Capture();

    return task.ContinueWith(res =>
        ExecutionContext.Run(ec, (_) => callback(task), null));
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top