使用 LoadControl 和对象初始值设定项来创建属性

发布于 2024-08-18 15:57:12 字数 2126 浏览 3 评论 0原文

过去,我使用 UserControls 创建电子邮件模板,我可以在其中填充属性,然后使用 LoadControl 和 RenderControl 获取用于电子邮件正文的 html。这是在 asp.net webforms 中。

我正在构建一个 mvc 网站,想做类似的事情。我实际上已经考虑过将此功能放在一个单独的类库中,并正在研究如何做到这一点,以便在我的 Web 层中我可以调用 EmailTemplate.SubscriptionEmail() ,然后它将从我的模板生成具有相关属性的 html地方(显然那里需要有电子邮件地址等参数)。

我想创建一个单一的渲染控制方法,我可以将一个字符串传递到作为我的模板的 UserControl 的路径。我在网上发现了这种适合我需求的方法:

public static string RenderUserControl(string path,
                 string propertyName, object propertyValue)
        {
            Page pageHolder = new Page();
            UserControl viewControl =
               (UserControl)pageHolder.LoadControl(path);

            if (propertyValue != null)
            {
                Type viewControlType = viewControl.GetType();
                PropertyInfo property = viewControlType.GetProperty(propertyName);
                if (property != null)
                    property.SetValue(viewControl, propertyValue, null);
                else
                {
                    throw new Exception(string.Format(
                       "UserControl: {0} does not have a public {1} property.",
                       path, propertyName));
                }
            }

            pageHolder.Controls.Add(viewControl);
            StringWriter output = new StringWriter();
            HttpContext.Current.Server.Execute(pageHolder, output, false);
            return output.ToString();
        }

我的问题是我的 UserControl 可能具有多个不同的属性。因此 SubscribeEmail 可能需要 FirstName 和 EmailAddress,而另一个电子邮件模板 UserControl(我们称之为 DummyEmail)将需要 FirstName、EmailAddress 和 DateOfBirth。

上面的方法似乎只携带一个参数 propertyName 和 propertyValue。我考虑了一个字符串数组,我可以将不同的属性放入其中,但后来我认为拥有一个对象初始化器会很酷,这样我就可以像这样调用该方法:

RenderUserControl("EmailTemplates/SubscribeEmail.ascs", new object() { Firstname="Lloyd", Email="[email protected]" })

这有意义吗?我只是想知道这是否可能首先以及我将如何实现它?我不确定是否可以将“对象”上设置的属性映射到加载的用户控件上的属性,以及是否可以从哪里开始执行此操作?

以前有人做过这样的事情吗?有人可以帮忙吗?

劳埃德

In the past I've used UserControls to create email templates which I can fill properties on and then use LoadControl and then RenderControl to get the html for which to use for the body text of my email. This was within asp.net webforms.

I'm in the throws of building an mvc website and wanted to do something similar. I've actually considered putting this functionality in a seperate class library and am looking into how I can do this so that in my web layer I can just call EmailTemplate.SubscriptionEmail() which will then generate the html from my template with properties in relevant places (obviously there needs to be parameters for email address etc in there).

I wanted to create a single Render control method for which I can pass a string to the path of the UserControl which is my template. I've come across this on the web that kind of suits my needs:

public static string RenderUserControl(string path,
                 string propertyName, object propertyValue)
        {
            Page pageHolder = new Page();
            UserControl viewControl =
               (UserControl)pageHolder.LoadControl(path);

            if (propertyValue != null)
            {
                Type viewControlType = viewControl.GetType();
                PropertyInfo property = viewControlType.GetProperty(propertyName);
                if (property != null)
                    property.SetValue(viewControl, propertyValue, null);
                else
                {
                    throw new Exception(string.Format(
                       "UserControl: {0} does not have a public {1} property.",
                       path, propertyName));
                }
            }

            pageHolder.Controls.Add(viewControl);
            StringWriter output = new StringWriter();
            HttpContext.Current.Server.Execute(pageHolder, output, false);
            return output.ToString();
        }

My issue is that my UserControl(s) may have multiple and differing properties. So SubscribeEmail may require FirstName and EmailAddress where another email template UserControl (lets call it DummyEmail) would require FirstName, EmailAddress and DateOfBirth.

The method above only appears to carry one parameter for propertyName and propertyValue. I considered an array of strings that I could put the varying properties into but then I thought it'd be cool to have an object intialiser so I could call the method like this:

RenderUserControl("EmailTemplates/SubscribeEmail.ascs", new object() { Firstname="Lloyd", Email="[email protected]" })

Does that make sense? I was just wondering if this is at all possible in the first place and how I'd implement it? I'm not sure if it would be possible to map the properties set on 'object' to properties on the loaded user control and if it is possible where to start in doing this?

Has anyone done something like this before? Can anyone help?

Lloyd

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

遥远的她 2024-08-25 15:57:12

你的例子可以实现。但它相当违反了 MVC 模式。如果您无论如何都这样做,那么您也可以采用与网络表单中完全相同的解决方案。

当我创建 html 邮件时,我通常会创建一个普通的 mvc 视图(在某些控制器和视图上执行操作)。然后我将该视图渲染为字符串并将其发送出去。这样,您就遵循了 mvc 模式,并且能够自动在浏览器中查看邮件(您可以只访问该操作的 url。当然,这可以以您想要的任何方式进行限制)。

为了将视图呈现为字符串,我使用此类:

public class ViewRenderer
{
    protected RouteCollection RouteCollection { get; private set; }

    public DefaultViewRenderer()
        : this(RouteTable.Routes)
    {

    }

    public DefaultViewRenderer(RouteCollection routeCollection)
    {
        RouteCollection = routeCollection;
    }

    public virtual string RenderViewAsString<TController>(Expression<Action<TController>> action)
        where TController : Controller
    {
        var sb = new StringBuilder();
        var memWriter = new StringWriter(sb);

        var fakeContext = CreateFakeContext(memWriter);

        var oldContext = HttpContext.Current;
        HttpContext.Current = fakeContext;

        CreateHtmlHelper(fakeContext).RenderAction(action);

        HttpContext.Current = oldContext;

        memWriter.Flush();
        memWriter.Dispose();
        return sb.ToString();
    }

    protected virtual HttpContext CreateFakeContext(StringWriter memWriter)
    {
        var fakeResponse = new HttpResponse(memWriter);
        var context = new HttpContext(HttpContext.Current.Request, fakeResponse);

        foreach (var key in HttpContext.Current.Items.Keys)
            context.Items[key] = HttpContext.Current.Items[key];

        foreach (string key in HttpContext.Current.Session.Keys)
            context.Session[key] = HttpContext.Current.Session[key];

        return context;
    }

    protected virtual HtmlHelper CreateHtmlHelper(HttpContext fakeContext)
    {
        var fakeControllerContext = CreateControllerContext(fakeContext, RouteCollection);
        return new HtmlHelper(new ViewContext(fakeControllerContext,
                                                  new FakeView(), new ViewDataDictionary(), new TempDataDictionary()), new FakePage());
    }

    protected virtual ControllerContext CreateControllerContext(HttpContext fakeContext, RouteCollection routeCollection)
    {
        return new ControllerContext(
            new HttpContextWrapper(fakeContext),
            routeCollection.GetRouteData(new HttpContextWrapper(HttpContext.Current)), new FakeController());
    }

    protected class FakeView : IView
    {
        public void Render(ViewContext viewContext, TextWriter writer)
        {
            throw new NotImplementedException();
        }
    }

    protected class FakeController : Controller
    {

    }

    protected class FakePage : IViewDataContainer
    {
        public ViewDataDictionary ViewData
        {
            get { return new ViewDataDictionary(); }
            set { }
        }
    }
}

它使用一个虚假响应,将视图的结果写入字符串构建器。然后您可以这样使用:

var viewRenderer = new ViewRenderer();
var body = viewRenderer.RenderViewAsString<SomeController>(x => x.ActionThatRendersHtmlMail(parameters));

然后您只需发送带有该正文的邮件。您当然可以将其包装在您自己的类中,以便您可以调用 EmailTemplate.SubscriptionEmail(); (从你的例子)。

Your example could be implemented. But it violates the MVC pattern quite a bit. And if you are doing that anyway you might just as well go with the exact same solution you had in webforms.

When I create html mails I usually create a normal mvc view (with a action on some controller and a view). Then I render that view into a string and send it away. This way you are following the mvc pattern and you get the ability to see the mail in the browser automatically (you can just visit the url to that action. This can of course be restricted in any way you want).

To render a view to a string I use this class:

public class ViewRenderer
{
    protected RouteCollection RouteCollection { get; private set; }

    public DefaultViewRenderer()
        : this(RouteTable.Routes)
    {

    }

    public DefaultViewRenderer(RouteCollection routeCollection)
    {
        RouteCollection = routeCollection;
    }

    public virtual string RenderViewAsString<TController>(Expression<Action<TController>> action)
        where TController : Controller
    {
        var sb = new StringBuilder();
        var memWriter = new StringWriter(sb);

        var fakeContext = CreateFakeContext(memWriter);

        var oldContext = HttpContext.Current;
        HttpContext.Current = fakeContext;

        CreateHtmlHelper(fakeContext).RenderAction(action);

        HttpContext.Current = oldContext;

        memWriter.Flush();
        memWriter.Dispose();
        return sb.ToString();
    }

    protected virtual HttpContext CreateFakeContext(StringWriter memWriter)
    {
        var fakeResponse = new HttpResponse(memWriter);
        var context = new HttpContext(HttpContext.Current.Request, fakeResponse);

        foreach (var key in HttpContext.Current.Items.Keys)
            context.Items[key] = HttpContext.Current.Items[key];

        foreach (string key in HttpContext.Current.Session.Keys)
            context.Session[key] = HttpContext.Current.Session[key];

        return context;
    }

    protected virtual HtmlHelper CreateHtmlHelper(HttpContext fakeContext)
    {
        var fakeControllerContext = CreateControllerContext(fakeContext, RouteCollection);
        return new HtmlHelper(new ViewContext(fakeControllerContext,
                                                  new FakeView(), new ViewDataDictionary(), new TempDataDictionary()), new FakePage());
    }

    protected virtual ControllerContext CreateControllerContext(HttpContext fakeContext, RouteCollection routeCollection)
    {
        return new ControllerContext(
            new HttpContextWrapper(fakeContext),
            routeCollection.GetRouteData(new HttpContextWrapper(HttpContext.Current)), new FakeController());
    }

    protected class FakeView : IView
    {
        public void Render(ViewContext viewContext, TextWriter writer)
        {
            throw new NotImplementedException();
        }
    }

    protected class FakeController : Controller
    {

    }

    protected class FakePage : IViewDataContainer
    {
        public ViewDataDictionary ViewData
        {
            get { return new ViewDataDictionary(); }
            set { }
        }
    }
}

This uses a fake response that writes the result of the view into a stringbuilder. Then you use this like this:

var viewRenderer = new ViewRenderer();
var body = viewRenderer.RenderViewAsString<SomeController>(x => x.ActionThatRendersHtmlMail(parameters));

Then you just send the mail with that body text. You can of course wrap this in you own class so you can call EmailTemplate.SubscriptionEmail(); (from your example).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文