【问题标题】:Fake ASMX Web Service Call虚假的 ASMX Web 服务调用
【发布时间】:2010-04-12 21:50:40
【问题描述】:

我构建了一个连接到 SQL Server 数据库的 .NET ASMX Web 服务。有一个 Web 服务调用 GetAllQuestions()。

 var myService = new SATService();
 var serviceQuestions = myService.GetAllQuestions();

我将GetAllQuestions的结果保存到本地应用程序文件夹中的GetAllQuestions.xml

有没有办法伪造网络服务调用并使用本地 xml 结果?

我只想获取整个 sql 表的内容,并自动为我生成具有相关属性名称的对象数组,就像使用 LINQ to SQL Web 服务一样。

请记住,我正在构建一个独立的 Monotouch iPhone 应用程序。

【问题讨论】:

  • 您可以添加代码以将 GetAllQuestions 的结果保存到 GetAllQuestions.xml 吗?
  • 没有代码。我亲自保存了它。

标签: c# .net xml web-services asmx


【解决方案1】:

使用dependency injection

//GetSATService returns the fake service during testing     
var myService = GetSATService(); 
var serviceQuestions = myService.GetAllQuestions();

或者,最好在对象的构造函数中设置 SATService 字段(因此构造函数需要设置 SATService。如果这样做,将更容易测试。

编辑:对不起,我会在这里详细说明。您在上面的代码中拥有的是一个耦合依赖项,您的代码在其中创建了它正在使用的对象。依赖注入或控制反转(IOC)模式会让你解耦这种依赖。 (或者简单地说,不要称之为“新”——让其他东西来做——你可以在消费者之外控制的东西。)

有几种方法可以做到这一点,它们显示在下面的代码中(cmets解释):

class Program
{
    static void Main(string[] args)
    {
        //ACTUAL usage
        //Setting up the interface injection
        IInjectableFactory.StaticInjectable = new ConcreteInjectable(1);

        //Injecting via the constructor
        EverythingsInjected injected = 
            new EverythingsInjected(new ConcreteInjectable(100));

        //Injecting via the property
        injected.PropertyInjected = new ConcreteInjectable(1000);

        //using the injected items
        injected.PrintInjectables();
        Console.WriteLine();

        //FOR TESTING (normally done in a unit testing framework)
        IInjectableFactory.StaticInjectable = new TestInjectable();
        EverythingsInjected testInjected = 
            new EverythingsInjected(new TestInjectable());
        testInjected.PropertyInjected = new TestInjectable();
        //this would be an assert of some kind
        testInjected.PrintInjectables(); 

        Console.Read();
    }

    //the inteface you want to represent the decoupled class
    public interface IInjectable { void DoSomething(string myStr); }

    //the "real" injectable
    public class ConcreteInjectable : IInjectable
    {
        private int _myId;
        public ConcreteInjectable(int myId) { _myId = myId; }
        public void DoSomething(string myStr)
        {
            Console.WriteLine("Id:{0} Data:{1}", _myId, myStr);
        }
    }

    //the place to get the IInjectable (not in consuming class)
    public static class IInjectableFactory
    {
        public static IInjectable StaticInjectable { get; set; }
    }

    //the consuming class - with three types of injection used
    public class EverythingsInjected
    {
        private IInjectable _interfaceInjected;
        private IInjectable _constructorInjected;
        private IInjectable _propertyInjected;

        //property allows the setting of a different injectable
        public IInjectable PropertyInjected
        {
            get { return _propertyInjected; }
            set { _propertyInjected = value; }
        }

        //constructor requires the loosely coupled injectable
        public EverythingsInjected(IInjectable constructorInjected)
        {
            //have to set the default with property injected
            _propertyInjected = GetIInjectable();

            //retain the constructor injected injectable
            _constructorInjected = constructorInjected;

            //using basic interface injection
            _interfaceInjected = GetIInjectable();
        }

        //retrieves the loosely coupled injectable
        private IInjectable GetIInjectable()
        {
            return IInjectableFactory.StaticInjectable;
        }

        //method that consumes the injectables
        public void PrintInjectables()
        {
            _interfaceInjected.DoSomething("Interface Injected");
            _constructorInjected.DoSomething("Constructor Injected");
            _propertyInjected.DoSomething("PropertyInjected");
        }
    }

    //the "fake" injectable
    public class TestInjectable : IInjectable
    {
        public void DoSomething(string myStr)
        {
            Console.WriteLine("Id:{0} Data:{1}", -10000, myStr + " For TEST");
        }
    }

以上是一个完整的控制台程序,您可以运行它来看看它是如何工作的。我尽量保持简单,但如果您有任何问题,请随时问我。

第二次编辑: 从 cmets 中可以清楚地看出,这是一种操作需求,而不是测试需求,因此实际上它是一个缓存。这是一些可用于预期目的的代码。同样,下面的代码是一个完整的工作控制台程序。

class Program
{
    static void Main(string[] args)
    {
        ServiceFactory factory = new ServiceFactory(false);
        //first call hits the webservice
        GetServiceQuestions(factory);
        //hists the cache next time
        GetServiceQuestions(factory);
        //can refresh on demand
        factory.ResetCache = true;
        GetServiceQuestions(factory);
        Console.Read();
    }

    //where the call to the "service" happens
    private static List<Question> GetServiceQuestions(ServiceFactory factory)
    {
        var myFirstService = factory.GetSATService();
        var firstServiceQuestions = myFirstService.GetAllQuestions();
        foreach (Question question in firstServiceQuestions)
        {
            Console.WriteLine(question.Text);
        }
        return firstServiceQuestions;
    }
}

//this stands in place of your xml file
public static class DataStore
{
    public static List<Question> Questions;
}

//a simple question
public struct Question
{
    private string _text;
    public string Text { get { return _text; } }
    public Question(string text)
    {
        _text = text;
    }
}

//the contract for the real and fake "service"
public interface ISATService
{
    List<Question> GetAllQuestions();
}

//hits the webservice and refreshes the store
public class ServiceWrapper : ISATService
{
    public List<Question> GetAllQuestions()
    {
        Console.WriteLine("From WebService");
        //this would be your webservice call
        DataStore.Questions = new List<Question>()
                   {
                       new Question("How do you do?"), 
                       new Question("How is the weather?")
                   };
        //always return from your local datastore
        return DataStore.Questions;
    }
}

//accesses the data store for the questions
public class FakeService : ISATService
{
    public List<Question> GetAllQuestions()
    {
        Console.WriteLine("From Fake Service (cache):");
        return DataStore.Questions;
    }
}

//The object that decides on using the cache or not
public class ServiceFactory
{
    public  bool ResetCache{ get; set;}
    public ServiceFactory(bool resetCache)
    {
        ResetCache = resetCache;
    }
    public ISATService GetSATService()
    {
        if (DataStore.Questions == null || ResetCache)
            return new ServiceWrapper();
        else
            return new FakeService();
    }
}

希望这会有所帮助。祝你好运!

【讨论】:

  • 听起来不错,但您能说得更具体点吗?我查看了 wiki 页面,无法找出我需要的 c# 代码。 GetSATService() 中有什么;
  • 超级简单的方法:GetSatService 是一种受保护的虚拟方法,默认情况下会返回您的服务。在您的测试中,您可以覆盖 GetSatService 以返回您自己的实现。或者你可以通过你的类构造函数传入你的实现。
  • @Bryan,我已经在上面详细说明了,其中包含一个完整的工作程序,显示了不同类型的注入。在这里提出任何问题,我会尽力提供帮助。主要是,只需选择一种对您的特定情况容易的注入类型,然后您就可以轻松进行测试。
  • 谢谢奥迪。学习注射和注射剂是非常有趣的。是否有可能不那么冗长的做事方式......我想使用与我的 sql 表相关的 linq 对象,因为我可以保存获取整个表的 linq Web 服务调用的 xml 结果,我希望只需保存它并以与调用服务相同的方式访问它。
  • 听起来您正试图缓存数据并只调用一次 Web 服务(或者可能只在必要时)。它是否正确?如果是这样,我将发布一些代码,向您展示如何使用注入(它真的很简单)和类似缓存的东西来做到这一点。不过可能需要一分钟。告诉我。
【解决方案2】:

当你说假电话时,你只是在测试客户端吗?

您可以使用 fiddler,拦截请求并将本地 xml 文件返回给客户端。那就不要乱用你的客户端代码了。

【讨论】:

  • 我该怎么做斯蒂芬?什么是提琴手?
  • fiddler 是一个 http 代理/调试器 - fiddler2.com/fiddler2 安装并运行。将列出您的所有 http 流量。然后创建一个 AutoResponder,输入 Web 服务的 Url 并选择您希望 fiddler 返回的本地文件。 Fiddler 将拦截对 web 服务的请求并返回本地 xml 文件。
  • 听起来它在网络环境中会很好地工作,但我正在使用 monotouch 构建一个 iPhone 应用程序。
【解决方案3】:

详细说明Audie的回答

使用 DI 可以得到你想要的。非常简单,您将创建一个您的真实对象和模拟对象都实现的接口

public interface IFoo
{} 

然后,您可以让您的 GetSATService 方法根据您的需要返回 MockSATSerivce 或真正的 SATService 对象。

您可以在此处使用 DI 容器(一些存储与具体类型映射的接口的对象)您可以使用所需类型引导容器。因此,对于单元测试,您可以构造一个模拟容器,将 MockSATService 注册为 IFoo 接口的实现者。

那么你将作为具体类型的容器,但接口

IFoo mySATService = Container.Resolve<IFoo>();

然后在运行时您只需更改容器,以便它使用运行时类型而不是模拟类型引导,但您的代码将保持不变(因为您将所有内容都视为 IFoo 而不是 SATService)

这有意义吗?

【讨论】:

  • 这是否意味着我必须声明 IFoo 类和类型?我想使用web服务对象的原因是因为类型和名称已经根据我的sql数据库自动生成了。
  • 是的,您必须声明接口和具体类型。 auto gen 代理类不会理解如何为您执行此操作。老实说,我认为您在尝试做的事情上过于依赖自动生成课程。您应该有一个 GetAllQuestions 提供程序对象,该对象了解如何在 WebService 和本地文件存储之间切换作为后备数据存储。这就是 DI 大放异彩的地方。您可以无缝地替换通用接口的实现。
【解决方案4】:

随着时间的推移,我发现一个有趣的方法是提取一个接口并创建一个包装类。这可以很好地适应 IoC 容器,并且在没有 IoC 容器的情况下也能正常工作。

测试时,创建传递虚假服务的类。正常使用时,只需调用空构造函数,它可能只是构造一个提供者或使用配置文件解析提供者。

    public DataService : IDataService
    {
        private IDataService _provider;

        public DataService()
        {
            _provider = new RealService();
        }

        public DataService(IDataService provider)
        {
            _provider = provider;
        }

        public object GetAllQuestions()
        {
            return _provider.GetAllQuestions();
        }
    }

【讨论】:

    猜你喜欢
    • 2013-03-27
    • 2012-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-12
    • 1970-01-01
    • 1970-01-01
    • 2011-03-31
    相关资源
    最近更新 更多