Использование LoadControl с инициализатором объекта для создания свойств
В прошлом я использовал UserControls для создания шаблонов электронной почты, которые я могу заполнить свойствами, а затем с помощью LoadControl, а затем RenderControl, чтобы получить HTML-код, который будет использоваться для основного текста моего электронного письма. Это было в рамках asp.net веб-форм.
Я в процессе создания веб-сайта MVC и хотел сделать что-то подобное. Я на самом деле подумал о том, чтобы поместить эту функциональность в отдельную библиотеку классов, и сейчас я изучаю, как я могу это сделать, чтобы в моем веб-слое я мог просто вызвать EmailTemplate.SubscriptionEmail(), который затем сгенерирует HTML из моего шаблона со свойствами в соответствующих места (очевидно, там должны быть параметры для адреса электронной почты и т. д.).
Я хотел создать единственный метод управления Render, для которого я могу передать строку в путь 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="myemail@mydomain.com" })
Имеет ли это смысл? Мне просто интересно, возможно ли это вообще вообще и как я это реализовал? Я не уверен, что можно ли сопоставить свойства, установленные для объекта, со свойствами в загруженном пользовательском элементе управления, и можно ли начать с этого?
Кто-нибудь делал что-то подобное раньше? Кто-нибудь может помочь?
Lloyd
1 ответ
Ваш пример может быть реализован. Но это немного нарушает схему MVC. И если вы делаете это в любом случае, вы также можете использовать то же решение, которое вы использовали в веб-формах.
Когда я создаю html-письма, я обычно создаю обычное представление mvc (с действием на некотором контроллере и представлением). Затем я преобразовываю это представление в строку и отправляю ее. Таким образом, вы следуете шаблону mvc и получаете возможность автоматически просматривать почту в браузере (вы можете просто перейти по ссылке на это действие. Конечно, это может быть ограничено любым удобным для вас способом).
Для рендеринга представления в строку я использую этот класс:
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(); (из вашего примера).