none
c# wcf как правильно организовать dto объекты RRS feed

  • Общие обсуждения

  • Здравствуйте! Использую wcf, ef, c#. Хотел бы организовать трехзвенную архитектуру приложения (ms sql + wcf + wpf client mvvm)

    Возникли теоретические вопросы. 

    1) Есть модель EF DbFirst. Создал dto объекты (не совсем плоские), реализовал методы интерфейса, все работает, когда данные возвращаются в xml.

    Dto так примерно сделаны (есть вложения других типов - не простых): 

    [DataContract]
        public class BookDTO
        {
            [DataMember] 
            public string ID { get; set; }
            [DataMember] 
            public string Name { get; set; }
            [DataMember] 
            public AuthorDTO Author { get; set; } // сложный тип
        }

    Но теперь хочу еще сделать службу rest. Json ответ при такой организации dto-сущности возвращать не хочет. Когда убираю поле AuthorDTO Author - json ответ приходит. Т.е. я правильно понимаю, что для rest надо только плоские dto? Что тогда вместо Author указывать в сущности BookDto? id автора? А клиент сам должен его дополнительным запросом по id подтягивать? Я читал, что запросы к службе надо минимизировать, в том числе и возвращать только необходимые данные.. Получается не туда и не сюда. 

    2) Хорошая ли практика иметь 2 реализации метода: один для xml данных (можно использовать сложные dto - удобно, почти как обычные объекты только без логики), другой для json? Т.е. GetBook и GetBookAsJson? Но тогда придется хранить по 2 сущности dto каждого объекта: совсем плоское Dto и составное Dto 

    3) Стоит ли организовывать Dto так?

    [DataContract]
        public class BookDTO
        {
            [DataMember] 
            public string ID { get; set; }
            [DataMember] 
            public string Name { get; set; }
            [DataMember] 
            public int AuthorId { get; set; }
            [DataMember] 
            public int AuthorName { get; set; }
            [DataMember] 
            public int AuthorBorn { get; set; }
        }
     

    Такой объект может стать очень большим.. порядка 20-30 полей, если из предметной области много чего подтягивать..

    4) какие еще практики и приемы посоветуете? Ну наподобие "используй automapper", может паттерн, в рамках которого можно организовать правильно службу?  Буду рад любому совету. А хорошему примеру службы буду еще больше рад

    Спасибо!

    3 февраля 2018 г. 21:49

Все ответы

  • Для полноты описания проблемы можете добавить текст кода типа AuthorDTO. Возможно в нем есть что-то, что не может однозначно быть записанным в JSON.
    4 февраля 2018 г. 9:49
  • Служба:

    [ServiceContract]
    public interface IJobService
    {
            [OperationContract]
            [WebGet(UriTemplate = "/GetJobById/{jobId}", ResponseFormat = WebMessageFormat.Json)]
            Job GetJobById(string jobId);
    }
    
     public class JobService : IJobService
        {
            static JobService()
            {
    		// €спользую AutoMapper, код не полный, остальные сущности по образцу и подобию
                Mapper.Initialize(cfg => {
                    cfg.CreateMap<tbl_job, Job>()
                        .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id))
                        .ForMember(dest => dest.Number, opt => opt.MapFrom(src => src.number))
                        .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.job_type.name))
                        .ForMember(dest => dest.Year, opt => opt.MapFrom(src => src.year))
                         .ForMember(dest => dest.Face, opt => opt.MapFrom(src => src.tbl_face))
                         .ForMember(dest => dest.Customer, opt => opt.MapFrom(src => src.tbl_customer))
                    cfg.CreateMap<tbl_face, Face>()
                       .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id))
                       .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.name))
                       .ForMember(dest => dest.MiddleName, opt => opt.MapFrom(src => src.midname))
                       .ForMember(dest => dest.Surname, opt => opt.MapFrom(src => src.lastname));
                });
            }
    
            public Job GetJobById(string jobId)
            {
                try
                {
                    using (DbEntities context = new DbEntities())
                    {
                        var job = context.tbl_job.SingleOrDefault(t => t.id.ToString() == jobId);
                        return Mapper.Map<Job>(job);
                    }
                }
                catch (Exception ex)
                {
                    FaultException fe = new FaultException(ex.Message, new FaultCode("GETJOB001"));
                    throw fe;
                }
            }

    DTO:

     [DataContract]
        public class Job
        {
            [DataMember]
            public int Id { get; set; }
    
            [DataMember]
            public string Number { get; set; }
    
            [DataMember]
            public string Type { get; set; }
    
            [DataMember]
            public int Year { get; set; }
    
            [DataMember]
            public DateTime DateBegin { get; set; }
    
            [DataMember]
            public DateTime DateEnd { get; set; }
    
            [DataMember]
            public Face Face { get; set; }
    
            [DataMember]
            public Customer Customer { get; set; }
        }
    	
    	 public class Face
        {
            [DataMember]
            public int Id { get; set; }
    
            [DataMember]
            public string Name { get; set; }
    
            [DataMember]
            public string MiddleName { get; set; }
    
            [DataMember]
            public string Surname { get; set; }
    
            [DataMember]
            public DateTime Birthdate { get; set; }
        }
    	
    	 [DataContract]
        public class Customer
        {
            [DataMember]
            public int Id { get; set; }
    
            [DataMember]
            public string Name { get; set; }
    
            [DataMember]
            public Organization Organization { get; set; }
        }
    	
    	 [DataContract]
        public class Organization
        {
            [DataMember]
            public int Id { get; set; }
    
            [DataMember]
            public string Name { get; set; }
    
            [DataMember]
            public string Address { get; set; }
        }
    	
    	

    Web config:

     <system.serviceModel>
        
          <!-- Список служб -->
           <services>
             <!-- Служба JobService -->
             <service name="JobWcfService.JobService" behaviorConfiguration="Default">
               <endpoint address="" binding="webHttpBinding" contract="JobWcfService.IJobService" behaviorConfiguration="webBehavior"/>
               <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
            </service>
           </services>
          
          <!-- Поведения -->
          <behaviors>
            <endpointBehaviors>
              <behavior name="webBehavior">
                <webHttp helpEnabled="true"/>
              </behavior>
            </endpointBehaviors>
            <serviceBehaviors>
              <!-- Поведение служб по умолчанию -->
              <behavior name="Default">
                <serviceMetadata httpGetEnabled="true"/>
              </behavior>
              <behavior name="">
                <!-- Раскрытие метаданных (при развертывании установить значение false) -->
                <serviceMetadata httpGetEnabled="true"/> 
                <!--  Получать при сбое подробные сведения об исключении для целей отладки (при развертывании установить значение false) -->
                <serviceDebug includeExceptionDetailInFaults="false" /> 
              </behavior>
            </serviceBehaviors>
          
          </behaviors>
        
          <!-- Список схем транспортных протоколов -->
          <protocolMapping>
             <!-- Конченая точка с базовым адресом, начинающимся с http, использует привязку webHttpBinding (поддержка REST/POX, XML, JSON) -->
            <add scheme="http" binding="webHttpBinding" />
            <!-- Конченая точка с базовым адресом, начинающимся с https, использует привязку basicHttpsBinding 
            <add scheme="https" binding="basicHttpsBinding" />-->
          </protocolMapping>
        
          <!-- Настройка поддержки нескольких привязок -->
          <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
      
        </system.serviceModel>
      
        <!-- Настройки веб-сервера -->
        <system.webServer>
          <modules runAllManagedModulesForAllRequests="true" />
          <!-- Посмотр корневого каталога веб-приложения. Перед развертыванием необходимо установить значение false -->
          <directoryBrowse enabled="true" />
        </system.webServer>

    Проблема в сущности Job, которая имеет в себе поля Face, Customer. 

     [DataMember]
            public Face Face { get; set; }
    
            [DataMember]
            public Customer Customer { get; set; }

    Как только я комментирую эти поля и комментирую строки инициализации в AutoMapper

          .ForMember(dest => dest.Face, opt => opt.MapFrom(src => src.tbl_face))
                         .ForMember(dest => dest.Customer, opt => opt.MapFrom(src => src.tbl_customer))

    то функция мне возвращает файл json с именем = запрашиваемый id. Например, 412653.json (всплывает окошко в IE с предложением открыть или сохранить этот файл. В файле json разметка с запрашиваемыми полями).

    6 февраля 2018 г. 8:06
  • У вас проблемы с именами, не стоит называть переменную по имени типа.

    6 февраля 2018 г. 10:18
  • К сожалению проблема не в этом (переименовал названия свойств и все равно результат прежний). IE выдает "Не удалось отобразить страницу, убедитесь что адрес http://localhost правильный и т.д.". Как error то получить/отобразить...
    6 февраля 2018 г. 11:53