locked
Custom Formatter Data Not Getting update RRS feed

  • Question

  • User-574293449 posted

    I am working in web api with ninject as dependency resolver

    Scenario:

    I need to generate a report as pdf which includes report data and the organisation address as report header.

    Current Implementation:

    I have 2 controllers namely OrganisationController and ReportController.

    • Organisation Controller have CRUD operation for organisation.
    • Report Controller has get method to get the report details based on id. Here it accepts application/json and application/pdf as Accept header
    • For pdf i created custom PDF Formater and implemented the pdf with MigraDoc tool.

    Problem:

    While creating pdf, i access organisation data for the header. Pdf generated correctly and it includes organisation data as well. After i updated organisation data using organisation controller put method. When i create report again, it shows the old organisation data.

    My suspects:

    • Report1 constructor calls only one time. So organisationLogic not reinitialized. It may due to incorrect dispose or ninject scope.

    MyCode:

    1. Controller Code:

        [RoutePrefix("api")]
        public class ReportController : ApiController
        {
            [Route("reports/{id:int}", Name = "GetReportById")]
            [Route("~/api/reports.{ext}/{id}")]
            public async Task<IHttpActionResult> GetReport(int id)
            {
                //Get Report Data here
    
                return Ok(new Report1Model());
            }
    
        }

    2. Report class:

    public class Report1 : IReport
    {
        private readonly IOrganisationDetailLogic _organisationLogic;
        public Report1(IOrganisationDetailLogic organisationLogic)
        {
            _organisationLogic = organisationLogic;
        }
        public async Task<MemoryStream> Create(object model)
        {
            MemoryStream stream = null;
    
            Document document = new Document();
    
                //Report Header
                SetHeader(section);
    
              //Report Data here
    
    
                //Footer
                SetFooter(section);
    
                //Render as PDF
    
            return stream;
        }
    
        private void SetHeader(Section section)
        {
           //Here we are getting organisation data
            var organisationDetail = _organisationLogic.GetActiveOrganisations().First();
    
        }
    }

    3. PdfMediaTypeFormatter

    public class PdfMediaTypeFormatter : MediaTypeFormatter
    {
    private readonly string mediaType = "application/pdf";
    Func<Type, bool> typeisIPdf = (type) => typeof(IPdf).IsAssignableFrom(type);
    Func<Type, bool> typeisIPdfCollection = (type) => typeof(IEnumerable<IPdf>).
    IsAssignableFrom(type);
    private readonly IReport _report;
    public PdfMediaTypeFormatter(IReport report)
    {
    SupportedMediaTypes.Add(new MediaTypeHeaderValue(mediaType));
    MediaTypeMappings.Add(new UriPathExtensionMapping("pdf", new MediaTypeHeaderValue(mediaType)));
    this._report = report;
    }
    public override bool CanReadType(Type type)
    {
    return false;
    }
    public override bool CanWriteType(Type type)
    {
    return typeisIPdf(type) || typeisIPdfCollection(type);
    }
    public async override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
    {
    var memoryStream = await _report.Create(value);
    
    var bytes = memoryStream.ToArray();
    await writeStream.WriteAsync(bytes, 0, bytes.Length);
    }
    }

    4. WebApi.config

        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                // Web API configuration and services
                config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
                config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                var formatter = (PdfMediaTypeFormatter)config.DependencyResolver.GetService(typeof(PdfMediaTypeFormatter));
                config.Formatters.Add(formatter);
    
                // Configure Web API to use only bearer token authentication.
                config.SuppressDefaultHostAuthentication();
                config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
    
                // Web API routes
                config.MapHttpAttributeRoutes();
    
                config.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    routeTemplate: "api/{controller}/{id}",
                    defaults: new { id = RouteParameter.Optional }
                );
            }
        }
    

    5.  ninject Register services method

            private static void RegisterServices(IKernel kernel)
            {
                kernel.Bind<IOrganisationDetailLogic>().To<OrganisationDetailLogic>().InRequestScope();
                kernel.Bind<IOrganisationDetailRepository>().To<OrganisationDetailRepository>().InRequestScope();
                kernel.Bind<IReport>().To<Report1>();
                kernel.Bind<PdfMediaTypeFormatter>().ToSelf();
            }

    Please help where i missed. When i initiated _organisationLogic in the SetHeader itself, its working correct

    Thursday, September 14, 2017 2:47 AM

All replies

  • User-271186128 posted

    Hi akhilrajau,

    Report1 constructor calls only one time. So organisationLogic not reinitialized. It may due to incorrect dispose or ninject scope.

    From your description, I suppose perhaps the issue is related to the cache. I suggest you could try to use Ctrl + F5 to refresh the page.

    Best regards,
    Dillion

    Friday, September 15, 2017 8:24 AM
  • User-574293449 posted

    I used POSTMAN to test the API. So any issues in related to cache?

    I tried this by calling pdf in postman and organisation update in swagger. First time report fine. After i changed using swagger and again try to generate report in postman but got same old data

    Note: I read that constructor injection not support in custom format. Is this right? then it may be the problem. Anyway article related to mvc and core

    https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-formatters#read-write 

    I added my custom class in the question

    Saturday, September 16, 2017 1:50 AM
  • User-1355817221 posted

    It seems that you haven't provided the complete code. It's hard to guess your problem.

    Sunday, September 17, 2017 2:45 PM
  • User-574293449 posted

    I updated all code in the question

    Monday, September 18, 2017 1:50 AM
  • User-271186128 posted

    Hi akhilrajau,

    I used POSTMAN to test the API. So any issues in related to cache?

    You could try to set a break point in the create report method. Then, in debug mode to check whether the method is execute and use the latest data.

    If the method only execute one time, it means the issue is related to the cache.

    You could refer to the following article, and set the Send no-cache header option.

    https://www.getpostman.com/docs/postman/launching_postman/settings

    I read that constructor injection not support in custom format. Is this right? then it may be the problem. Anyway article related to mvc and core

    https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-formatters#read-write 

    From above article, I can't find the related article say " constructor injection not support in custom format".

    Best regards,
    Dillion

    Monday, September 18, 2017 10:16 AM
  • User-574293449 posted

    Thanks.

    I tried by breakpoints in WriteToStreamAsync method of PdfMediaTypeFormatter class, Create method in Report1 class and constructor of Report1 class. The methods are executed except the constructor. 

    Regarding constructor injection support i mentioned this area. May be i understood it wrong

    'You can't do constructor dependency injection in a formatter class. For example, you can't get a logger by adding a logger parameter to the constructor. To access services, you have to use the context object that gets passed in to your methods. A code example below shows how to do this.'

    please advise

    Please help

    Tuesday, September 19, 2017 1:24 AM
  • User-1355817221 posted

    So you have solved your original problem?

    Your code is incomplete  and complicated. It's a little difficult to test.

    Wednesday, September 20, 2017 5:44 AM
  • User-574293449 posted

    not yet.. The Report1 class initiated only one time not based on request. But didn't get why

    Update:

    I created one new action in controller and called report1 create method. In that case i am getting the update data. 

    The issue happened when i working through formatter only. Can anyone help me

    Thursday, September 21, 2017 2:32 AM
  • User-271186128 posted

    Hi akhilrajau,

    ninject Register services method

            private static void RegisterServices(IKernel kernel)
            {
                kernel.Bind<IOrganisationDetailLogic>().To<OrganisationDetailLogic>().InRequestScope();
                kernel.Bind<IOrganisationDetailRepository>().To<OrganisationDetailRepository>().InRequestScope();
                kernel.Bind<IReport>().To<Report1>();
                kernel.Bind<PdfMediaTypeFormatter>().ToSelf();
            }

    I suggest you could try to modify above code as below:

    private static void RegisterServices(IKernel kernel)
            {
                kernel.Bind<IOrganisationDetailLogic>().To<OrganisationDetailLogic>().InRequestScope();
                kernel.Bind<IOrganisationDetailRepository>().To<OrganisationDetailRepository>().InRequestScope();
                kernel.Bind<IReport>().To<Report1>().InRequestScope();
                kernel.Bind<PdfMediaTypeFormatter>().ToSelf();
            }

    Best regards,
    Dillion

    Monday, September 25, 2017 10:00 AM
  • User-574293449 posted

    I also tried that but didn't worked.

    As of now, i have a work around. I can get the latest organisation data in the controller itself. After that i can include in the Report model so that it will access in the Report instead of getting data  from the database in Report class.

    But still i confused why its happened

    Tuesday, September 26, 2017 1:35 AM