smsohan / MvcMailer

A Mailer for ASP.Net MVC that forms the Email Body using MVC Views (Razor etc.) following Ruby on Rails ActionMailer style
MIT License
585 stars 176 forks source link

MvcMailer and Webapi #126

Open levka9 opened 10 years ago

levka9 commented 10 years ago

Hello, then i send email from mvc control it is send, but then i send the same email from web api control the email come empty with empty view.

May i change something and make it works ?

jamesbascle commented 10 years ago

Hi, without some code to reproduce the issue, I am unable to provide a direct answer.

However, since WebAPI and MVC can conflict in numerous ways, I recommend to you to create a separate MVC project which exposes only those methods which will end up sending an email. Don't expose any of the MVC internals outside this new project, just provide some simple methods which take a DTO object that contains the information you need to build your email.

This will not only help to resolve issues with MVC/WebAPI/MVCMailer, but also help provide a cleaner seperation of concerns in your architecture.

levka9 commented 10 years ago

thank for your fast reply, below I attach the code:

Api control:

namespace SMS.Sender.API
{
    public class AccountController : ApiController
    {
    [System.Web.Http.HttpGet]
    public object ForgotPassword(string email)
    {
            AccountMailer accountMailer = new AccountMailer();
            accountMailer.ResetPassword(email, newPassword).SendAsync();

            return true;
    }
}
}

Account Mailer class:

using Mvc.Mailer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using WebMatrix.WebData;

namespace SMS.Sender.Areas.Website.Models.Mailer
{
    public class AccountMailer : MailerBase
    {
        public AccountMailer()
    {
        MasterName = "_MailerLayout";
    }

    public virtual MvcMailMessage ResetPassword(string mailTo, string password)
    {
        ViewBag.Password = password;

        return Populate(x =>
        {
            x.Subject = "sms4israel - new password";
            x.ViewName = "ResetPassword";
            x.To.Add(mailTo);
        });
    }
}
}

You may try execute you own mvcmailer code in web api. This doesn't works also from different project.

jamesbascle commented 10 years ago

I've been able to reproduce the issue. It seems that when the controller context is created by the MailerBase, the httpcontext causes an instance of the WebApi controller context to be created, and so ViewExistsText/ViewExistsHtml return false, thus the rendering of the mail message does not happen and you get a blank mail with your original subject.

I need to see if there is any way to hack around this. I think this fundamentally arises due to the close-knit intermixing of the Razor view engine, MVC, and the HttpContext and ControllerContext. There may be a way to untie it all, but it's not terribly obvious how to use the razor engine without MVC.

jamesbascle commented 10 years ago

@levka9 A temporary workaround could be to have your mailer project have controller actions that send emails hosted on a localhost port that your WebApi controller calls. This would in theory allow the proper controller contexts to work and still support a seperation of concerns, though it would be a little heavier in terms of resource use, as you'd have to serialize your arguments to your mail message and send them over the local loopback.

Perhaps @smsohan can comment on another way to do this, or comment on a way to create the correct controller context?

gzurbach commented 10 years ago

@levka9 I'm having the same issue. Where you able to figure out a workaround by any chance?

levka9 commented 10 years ago

@gzurbach I just call action control from web api control. This is not ideal solution but this work.

gzurbach commented 10 years ago

I think I figured out a workaround (took me a few hours of digging). In my case I have:

The views are simply not found from the Web API project because they are located in the referenced assembly (the MvcMailler project). It's a very common issue apparently. I fixed it by installing the EmbeddedResourceVirtualPathProvider Nuget package (https://github.com/mcintyre321/EmbeddedResourceVirtualPathProvider) and it worked (almost) out of the box!

To understand better what's going on:

Hope it helps :)

levka9 commented 10 years ago

@gzurbach Finally, you call Action Control with MvcMailer from Web API control, it so ?

gzurbach commented 10 years ago

Yes, this is true. But this solution seems ok to me. Why do you think this is not ideal? I'm basically doing exactly what your're doing in the example above. Is what you're trying to accomplish different?

[HttpPost]
[Route("foo")]
public void PasswordChanged()
{
    UserMailer userMailer = new UserMailer();
    userMailer.PasswordChanged("foo@bar.com").Send();
}
levka9 commented 10 years ago

Because then we call MVC Action Control we through over all page lifecycle :

http://www.asp.net/mvc/tutorials/mvc-5/lifecycle-of-an-aspnet-mvc-5-application

I we call Web API Control and then MVC Action Control. I thought it twice over all page lifecycle.

The ideal way is call Mvc Mailer from Web API Control then we need to call it from Web API, but context of Web API it's different from MVC Action. That why we can't call Mvc Mailer from Web API Control.

gzurbach commented 10 years ago

Alright I see your point. I think I will accommodate with that. MvcMailer is great, even after 2 page lifecycles! Maybe a future version will fix this issue :)