I need an html helper that would take care of "tabs" functionality on a page. clicking on the tabs will re-load the page and reload the partial view (if specified). I wrote it like so but not sure it this is the best solution.??
public static class TabExtensions
{
public static MvcHtmlString Tabs(this HtmlHelper htmlHelper, List<TabItem> tabItems, object htmlAttributes = null)
{
if (tabItems == null)
{
throw new ArgumentException("at least one tab item required");
}
string viewName = string.Empty;
object model = null;
var sb = new StringBuilder();
sb.Append("<a name=\"tabs\"></a>");
var tagUl = new TagBuilder("ul");
tagUl.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
// Current url data
var baseUri = new UriBuilder(htmlHelper.ViewContext.HttpContext.Request.Url);
var selTab = htmlHelper.ViewContext.RequestContext.HttpContext.Request.QueryString["tab"];
foreach (var tab in tabItems)
{
// No tab user selected
if (string.IsNullOrEmpty(selTab))
{
selTab = tab.TabLinkText;
}
var tagLi = new TagBuilder("li");
string tagInnerHtml;
if (selTab.Equals(tab.TabLinkText, StringComparison.OrdinalIgnoreCase))
{
tagLi.MergeAttribute("class", "current");
tagInnerHtml = string.Format("<strong>{0}</strong>", tab.Text);
viewName = tab.PartialViewName;
model = tab.PartialViewModel;
}
else
{
tagInnerHtml = tab.Text;
}
var queryToAppend = string.Concat("tab=", tab.TabLinkText);
var querystring = new StringBuilder();
if (baseUri.Query.Length > 1)
{
if (baseUri.Query.Contains("tab"))
{
querystring.Append(baseUri.Query.Replace(string.Concat("tab=", selTab), queryToAppend));
}
else
{
querystring.Append(baseUri.Query + "&" + queryToAppend);
}
}
else
{
querystring.Append("?" + queryToAppend);
}
// Assign anchor link
querystring.Append("#tabs");
tagLi.InnerHtml = string.Format("{1}", querystring, tagInnerHtml);
tagUl.InnerHtml += tagLi.ToString();
}
sb.Append(tagUl.ToString());
// Render partial
if (!string.IsNullOrEmpty(viewName))
{
htmlHelper.ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(htmlHelper.ViewContext.Controller.ControllerContext, viewName);
ViewContext viewContext = new ViewContext(htmlHelper.ViewContext.Controller.ControllerContext, viewResult.View, htmlHelper.ViewData, htmlHelper.ViewContext.TempData, sw);
viewResult.View.Render(viewContext, sw);
sb.Append(sw.GetStringBuilder().ToString());
}
}
return MvcHtmlString.Create(sb.ToString());
}
}
#region Tab Item Model
public class TabItem
{
public string Text { get; set; }
public string TabLinkText { get; set; }
public string PartialViewName { get; set; }
public object PartialViewModel { get; set; }
public TabItem(string text, string tabLinkText)
: this()
{
this.Text = text;
this.TabLinkText = tabLinkText;
}
public TabItem(string text, string tabLinkText, string partialViewName, object partialViewModel = null)
: this()
{
this.Text = text;
this.TabLinkText = tabLinkText;
this.PartialViewName = partialViewName;
this.PartialViewModel = partialViewModel;
}
public TabItem()
{
this.Text = string.Empty;
this.PartialViewName = string.Empty;
this.TabLinkText = string.Empty;
this.PartialViewModel = null;
}
}
#endregion
You use it like so:
<%
var tabList = new List<TabItem>
{
new TabItem(LocalResources.fld_AboutFirm_lbl, "about"),
new TabItem(LocalResources.fld_FirmOffers_lbl, "offer"),
new TabItem(LocalResources.fld_Profile_lbl, "profile", "~/Views/Partial/FirmProfileTab.cshtml", Model),
new TabItem(LocalResources.fld_Contact_lbl, "contact", "~/Views/Partial/FirmContactTab.cshtml", Model)
};
%>
<%: Html.Tabs(tabList, new { #class = "firmTabs clearfix" })%>
this will generate html:
<ul class="firmTabs clearfix"><li>O firmie</li><li>Firma oferuje</li><li>Profil</li><li class="current"><strong>Kontakt</strong></li></ul>
I think the better solution is to make master page for tab menu and view pages for tab content. Your approach looks very complex for me. Why do you need html helper for this? If you encapsulate your html into helper method - you loose your view. So in terms of MVC your way is not good I think.
Related
I'm looking to add records to an Umbraco v8 form. I know I need the form guid. Is this how I'd do it? Something like this?
public void PostFormData()
{
Guid FormGuid = new Guid("8494a8f0-94da-490e-bd61-7e658c226142");
var form = _formService.Get(FormGuid);
//place for field data into fieldDic
var fieldDic = new Dictionary<Guid, RecordField>();
var firstName = form.AllFields.First(f => f.Alias == "firstName");
var firstNameRecord = new RecordField(firstName);
firstNameRecord.Values = new List<object>() { "Mad Max" };
fieldDic.Add(firstName.Id, firstNameRecord);
var record = new Record()
{
Created = DateTime.Now,
Form = form.Id,
RecordFields = fieldDic,
State = FormState.Submitted,
};
record.RecordData = record.GenerateRecordDataAsJson();
_recordStorage.InsertRecord(record, form);
}
Here's how I do it. Note, I'm hard-coding the Record.UmbracoPageId to -1 while you might want to actually pass in the correct page ID.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Forms.Core.Data.Storage;
using Umbraco.Forms.Core.Models;
using Umbraco.Forms.Core.Persistence.Dtos;
using Umbraco.Forms.Core.Services;
namespace myProject.Services
{
public class FormServiceComposer : IUserComposer
{
public void Compose(Composition composition)
{
composition.Register<IFormService, FormService>(Lifetime.Request);
}
}
public interface IFormService
{
void InsertFormData(Guid formGuid, object formModel, string ipAddress);
}
public class FormService : IFormService
{
private readonly ILogger _logger;
private readonly Umbraco.Forms.Core.Services.IFormService _formService;
private readonly IRecordStorage _recordStorage;
private readonly IRecordFieldStorage _recordFieldStorage;
private readonly IWorkflowService _workflowService;
public FormService(ILogger logger, Umbraco.Forms.Core.Services.IFormService formService, IRecordStorage recordStorage, IRecordFieldStorage recordFieldStorage, IWorkflowService workflowService)
{
_logger = logger;
_formService = formService;
_recordStorage = recordStorage;
_recordFieldStorage = recordFieldStorage;
_workflowService = workflowService;
}
#region IFormService
public void InsertFormData(Guid formGuid, object formModel, string ipAddress)
{
try
{
Form form = _formService.GetForm(formGuid);
Record record = new Record();
foreach (Field field in form.AllFields)
{
string caption = CleanCaption(field.Caption);
if (formModel.GetType().GetProperty(caption) == null) continue;
var propertyValue = formModel.GetType().GetProperty(caption).GetValue(formModel, null);
if (propertyValue != null)
{
List<object> values = ExtractValues(propertyValue);
RecordField recordField = new RecordField
{
Alias = field.Alias,
FieldId = field.Id,
Field = field,
Key = Guid.NewGuid(),
Record = record.Id,
Values = values
};
_recordFieldStorage.InsertRecordField(recordField);
record.RecordFields.Add(recordField.Key, recordField);
}
}
record.Form = formGuid;
record.IP = ipAddress;
record.UmbracoPageId = -1;
record.State = Umbraco.Forms.Core.Enums.FormState.Approved;
record.RecordData = record.GenerateRecordDataAsJson();
_recordStorage.InsertRecord(record, form);
_recordStorage.DisposeIfDisposable();
}
catch (Exception ex)
{
_logger.Error<FormService>(ex, "Failed inserting Umbraco Forms data for {formGuid}");
}
}
#endregion IFormService
#region Private
private string CleanCaption(string caption)
{
Regex rgx = new Regex("[^a-zA-Z0-9 -]");
return rgx.Replace(caption.Trim().Replace(" ", ""), "");
}
private List<object> ExtractValues(object propertyValue)
{
List<object> result = new List<object>();
if (propertyValue is string == false && propertyValue.GetType().GetGenericTypeDefinition() == typeof(List<>))
{
IEnumerable<object> _propertyValue = (IEnumerable<object>)propertyValue;
if (_propertyValue.Any())
{
if (_propertyValue.First().GetType().GetProperties().Count() > 1)
{
JArray _properties = JArray.Parse(JsonConvert.SerializeObject(propertyValue));
foreach (JToken item in _properties)
{
string _value = string.Empty;
foreach (var _property in _propertyValue.First().GetType().GetProperties())
{
string _key = _property.Name;
_value = _value + (_value == "" ? "" : " - ") + item[_key].ToString();
}
result.Add(_value);
}
}
else
{
string _key = _propertyValue.First().GetType().GetProperties().First().Name;
JArray _properties = JArray.Parse(JsonConvert.SerializeObject(propertyValue));
foreach (JToken item in _properties)
{
result.Add(item[_key].ToString());
}
}
}
}
else
{
result.Add(propertyValue);
}
return result;
}
#endregion Private
}
}
I need to display check box list with more than one options. User must select minimum one check box, user can select more than one check boxes.
I want to store values of all check boxes (selected) in one field as a string(Data base) with coma separated. This is not mandatory, mandatory is need to store multiple values of each selected check box. Alternate solutions are welcome.
Model
public class Member
{
public string Member_VehicalType { get; set; }
public IList<SelectListItem> Member_VehicalType_List { get; set; }
Controller
[HttpGet]
public ActionResult Create()
{
Member objMemberModel = new Member();
List<SelectListItem> vehical_Types = new List<SelectListItem>();
vehical_Types.Add(new SelectListItem { Text = "Two Wheeler", Value = "1" });
vehical_Types.Add(new SelectListItem { Text = "Four Wheeler", Value = "2" });
objMemberModel.Member_VehicalType_List = vehical_Types;
return View(objMemberModel);
How do I create view with #Html.CheckBoxFor( or #Html.CheckBox(
I recently had to deal with SelectListItem as a checkbox-list. I came up with the following HtmlExtensions, which might be helpful. These extensions provide the same functionality as the #Html.DropDownListFor(model => ...);
Usage: #Html.CheckBoxListFor(model => model.ModelMemberToPutValueIn, Model.Member_VehicalType_List)
public static class HtmlExtensions
{
public static MvcHtmlString CheckBoxListFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, IEnumerable<SelectListItem> items, object htmlAttributes = null)
{
var listName = ExpressionHelper.GetExpressionText(expression);
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
items = GetCheckboxListWithDefaultValues(metaData.Model, items);
return htmlHelper.CheckBoxList(listName, items, htmlAttributes);
}
public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string listName, IEnumerable<SelectListItem> items, object htmlAttributes = null)
{
if (items == null) return null;
var container = new TagBuilder("div");
container.AddCssClass("checkbox-list");
container.MergeAttribute("id", HtmlHelper.GenerateIdFromName(listName));
foreach (var item in items)
{
var id = HtmlHelper.GenerateIdFromName(String.Join(".", listName, item.Text));
var div = new TagBuilder("div");
div.AddCssClass("checkbox");
var cb = new TagBuilder("input");
cb.MergeAttribute("type", "checkbox");
cb.MergeAttribute("id", id);
cb.MergeAttribute("name", listName);
cb.MergeAttribute("value", item.Value ?? item.Text);
if (item.Selected) cb.MergeAttribute("checked", "checked");
var label = new TagBuilder("label");
label.MergeAttribute("for", id);
label.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
label.InnerHtml = item.Text;
div.InnerHtml = cb.ToString(TagRenderMode.SelfClosing) + label;
container.InnerHtml += div;
}
return new MvcHtmlString(container.ToString());
}
private static IEnumerable<SelectListItem> GetCheckboxListWithDefaultValues(object defaultValues, IEnumerable<SelectListItem> selectList)
{
var defaultValuesList = defaultValues as IEnumerable;
if (defaultValuesList == null) return selectList;
var values = from object value in defaultValuesList select Convert.ToString(value, CultureInfo.CurrentCulture);
var selectedValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase);
var newSelectList = new List<SelectListItem>();
foreach (var item in selectList)
{
item.Selected = (item.Value != null) ? selectedValues.Contains(item.Value) : selectedValues.Contains(item.Text);
newSelectList.Add(item);
}
return newSelectList;
}
}
I want to create a generic checkbox list view model and so I got this:
public class ChckboxListViewModel<T>
{
public List<CheckboxViewModel<T>> CheckboxList { get; set; }
public IEnumerable<T> SelectedValues
{
get { return CheckboxList.Where(c => c.IsSelected).Select(c => c.Value); }
}
public ChckboxListViewModel()
{
CheckboxList = new List<CheckboxViewModel<T>>();
}
}
public class CheckboxViewModel<T>
{
public string Label { get; set; }
public T Value { get; set; }
public bool IsSelected { get; set; }
public CheckboxViewModel(string i_Label, T i_Value, bool i_IsSelected)
{
Label = i_Label;
Value = i_Value;
IsSelected = i_IsSelected;
}
}
It is used by a different view model to represent filters of different statuses:
public class FaultListFilters
{
public string SearchKeyword { get; set; }
public ChckboxListViewModel<Fault.eFaultStatus> StatusFilter { get; set; }
public FaultListFilters()
{
SearchKeyword = null;
StatusFilter = new ChckboxListViewModel<Fault.eFaultStatus>();
StatusFilter.CheckboxList.Add(new CheckboxViewModel<Fault.eFaultStatus>(FaultManagementStrings.OpenStatus,Fault.eFaultStatus.Open,true));
StatusFilter.CheckboxList.Add(new CheckboxViewModel<Fault.eFaultStatus>(FaultManagementStrings.InProgressStatus, Fault.eFaultStatus.InProgress, true));
StatusFilter.CheckboxList.Add(new CheckboxViewModel<Fault.eFaultStatus>(FaultManagementStrings.ClosedStatus, Fault.eFaultStatus.Close, false));
}
}
Now I can't find the right way to display the editors or to create an editor template for that kind of a view model because it is Generic.
I don't want o create a separate editor template for ChckboxListViewModel<int> and then another for ChckboxListViewModel<Fault.eFaultStatus> and so on..
Is it even a goose idea to use generics in this case?
Is there another way to represent and display a check-box list in MVC?
I have done the following but the modle is not binding for some reason:
#using (Html.BeginForm("FaultManagement", "Faults", FormMethod.Get, null))
{
for (int i=0 ; i<Model.FaultListFilters.StatusFilter.CheckboxList.Count() ; i++)
{
#Html.HiddenFor(m => m.FaultListFilters.StatusFilter.CheckboxList[i].Value)
#Html.CheckBoxFor(m => m.FaultListFilters.StatusFilter.CheckboxList[i].IsSelected)
#Html.LabelFor(m=> m.FaultListFilters.StatusFilter.CheckboxList[i].IsSelected,Model.FaultListFilters.StatusFilter.CheckboxList[i].Label)
}
<input type="submit" />
}
Is it even a goose idea to use generics in this case?
Don't think it is.
Is there another way to represent and display a check-box list in MVC?
I would write a custom HTML helper:
public static class HtmlExtensions
{
public static IHtmlString CheckboxListFor<TModel>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, IEnumerable<string>>> ex,
IEnumerable<string> possibleValues)
{
var metadata = ModelMetadata.FromLambdaExpression(ex, html.ViewData);
var availableValues = (IEnumerable<string>)metadata.Model;
var name = ExpressionHelper.GetExpressionText(ex);
return html.CheckboxList(name, availableValues, possibleValues);
}
private static IHtmlString CheckboxList(this HtmlHelper html, string name, IEnumerable<string> selectedValues, IEnumerable<string> possibleValues)
{
var result = new StringBuilder();
foreach (string current in possibleValues)
{
var label = new TagBuilder("label");
var sb = new StringBuilder();
var checkbox = new TagBuilder("input");
checkbox.Attributes["type"] = "checkbox";
checkbox.Attributes["name"] = name;
checkbox.Attributes["value"] = current;
var isChecked = selectedValues.Contains(current);
if (isChecked)
{
checkbox.Attributes["checked"] = "checked";
}
sb.Append(checkbox.ToString());
sb.Append(current);
label.InnerHtml = sb.ToString();
result.Append(label);
}
return new HtmlString(result.ToString());
}
}
Then you could have a view model:
public class FaultListFiltersViewModel
{
public IEnumerable<string> SelectedStatusFilters { get; set; }
public IEnumerable<string> AvailableStatusFilters
{
get
{
return new[] { "Label 1", "Label 2", "Label 3" }
}
}
}
and inside the view you could use the helper:
#Html.CheckBoxListFor(x => x.SelectedStatusFilters, Model.AvailableStatusFilters)
Here is another implementation that will better support bootstrap button-group labels (as it requires them to be seperated) and enum type selected values.
public static IHtmlString CheckboxListFor<TModel, TKey>(this HtmlHelper<TModel> helper, Expression<Func<TModel, IEnumerable<TKey>>> ex, Dictionary<TKey, string> i_PossibleOptions, object i_LabelHtmlAttributes)
where TKey : struct, IConvertible
{
var metadata = ModelMetadata.FromLambdaExpression(ex, helper.ViewData);
var selectedValues = (IEnumerable<TKey>)metadata.Model;
var name = ExpressionHelper.GetExpressionText(ex);
return helper.CheckboxList(name, selectedValues, i_PossibleOptions, i_LabelHtmlAttributes);
}
private static IHtmlString CheckboxList<TKey>(this HtmlHelper helper, string name, IEnumerable<TKey> i_SelectedValues, Dictionary<TKey, string> i_PossibleOptions, object i_LabelHtmlAttributes)
where TKey : struct, IConvertible
{
if (!typeof(TKey).IsEnum) throw new ArgumentException("T must be an enumerated type");
var result = new StringBuilder();
foreach (var option in i_PossibleOptions)
{
var label = new TagBuilder("label");
label.MergeAttributes(new RouteValueDictionary(i_LabelHtmlAttributes));
label.Attributes["for"] = string.Format("{0}",option.Key.ToString());
label.InnerHtml = option.Value;
var checkbox = new TagBuilder("input");
checkbox.Attributes["type"] = "checkbox";
checkbox.Attributes["name"] = name;
checkbox.Attributes["id"] = string.Format("{0}", option.Key.ToString());
checkbox.Attributes["value"] = option.Key.ToString();
bool isChecked = ((i_SelectedValues != null) && (i_SelectedValues.Contains(option.Key)));
if ( isChecked )
{
checkbox.Attributes["checked"] = "checked";
}
result.Append(checkbox);
result.Append(label);
}
return new HtmlString(result.ToString());
}
And then the View Model looks like that:
public class FaultListFilters
{
[Display(ResourceType = typeof(FaultManagementStrings), Name = "SearchKeyword")]
public string SearchKeyword { get; set; }
public Dictionary<Fault.eFaultStatus, string> PossibleFaultStatuses
{
get
{
var possibleFaultStatuses = new Dictionary<Fault.eFaultStatus, string>();
possibleFaultStatuses.Add(Fault.eFaultStatus.Open, FaultManagementStrings.OpenStatus);
possibleFaultStatuses.Add(Fault.eFaultStatus.InProgress, FaultManagementStrings.InProgressStatus);
possibleFaultStatuses.Add(Fault.eFaultStatus.Close, FaultManagementStrings.ClosedStatus);
return possibleFaultStatuses;
}
}
public IEnumerable<Fault.eFaultStatus> SelectedFaultStatuses { get; set; }
public FaultListFilters()
{
SearchKeyword = null;
SelectedFaultStatuses = new[] { Fault.eFaultStatus.Open, Fault.eFaultStatus.InProgress };
}
}
and the usage remains the same (except i have added the label html attributes)
<div class="btn-group">
#Html.CheckboxListFor(m => m.FaultListFilters.SelectedFaultStatuses, Model.FaultListFilters.PossibleFaultStatuses, new { Class="btn"})
</div>
I've got the following code in my master page to load some customization stuff, like a css file, some address info in the footer, a header/footer logo etc. And I'm faced with load times of up to a minute! This is terrible practice, and I know - I just hacked it out to make it work. What would be the best practice of loading this type of customization information?
Currently, I try to load a cookie - check if it has the required keys, and if it doesnt - load the information from the database. I would like to have something similar to a .config file that's cached on the clients machine, if possible.
All the information is stored in a table called StoreSettings, which is linked to the Reseller table with StoreSettingsID.
<head id="Head1">
<%
HttpCookie storeSettingsCookie = Request.Cookies["StoreSettings"];
try
{
if (storeSettingsCookie == null || storeSettingsCookie.HasKeys == false)
{
if (Context.User.Identity.IsAuthenticated && Context.User.IsInRole("Reseller"))
{
var reseller = new Reseller();
var storeSettings = new StoreSettings();
var resellerRepository = new ResellerRepository();
reseller = resellerRepository.GetResellerByUsername(Context.User.Identity.Name);
if (reseller.StoreSettingsID != null && reseller.StoreSetting.Theme != null)
{
var storeSettingsRepository = new StoreSettingsRepository();
storeSettings = storeSettingsRepository.GetStoreSettings((int)reseller.StoreSettingsID);
storeSettingsCookie = new HttpCookie("StoreSettings");
storeSettingsCookie["HeaderImage"] = storeSettings.Image1.FileName;
storeSettingsCookie["FooterImage"] = storeSettings.Image.FileName;
storeSettingsCookie["ThemeLocation"] = storeSettings.Theme.StylesheetLocation;
storeSettingsCookie["StoreName"] = storeSettings.StoreName;
storeSettingsCookie["Address1"] = storeSettings.Address1;
storeSettingsCookie["Address2"] = storeSettings.Address2;
storeSettingsCookie["City"] = storeSettings.City;
storeSettingsCookie["PostalCode"] = storeSettings.PostalCode;
storeSettingsCookie["ProvinceCode"] = storeSettings.Province.Abbreviation;
storeSettingsCookie["Phone"] = storeSettings.Phone;
Response.Cookies.Add(storeSettingsCookie);
}
else
{
storeSettingsCookie = new HttpCookie("StoreSettings");
storeSettingsCookie["ThemeLocation"] = "~/Content/jquery-ui-1.8.9.custom.css";
storeSettingsCookie["StoreName"] = "";
storeSettingsCookie["Address1"] = "";
Response.Cookies.Add(storeSettingsCookie);
}
}
}
}
catch
{
}
%>
<title>
<asp:ContentPlaceHolder ID="TitleContent" runat="server" />
|
<%: string.IsNullOrEmpty(storeSettingsCookie["StoreName"]) ? "My Store Name" : storeSettingsCookie["StoreName"] %>
</title>
... <%-- Css/JS --%>
</head>
Any suggestions are appreciated. I expect a lot of sighs from the MVC guys. I know this isn't how MVC is supposed to work, so please refrain from reminding me of that, haha. :)
edit
Okay, so following LukLed's advice, I've created a base controller class, stuck the code above in its constructor and had my controllers inherit it. This appears like it is going to work, however the User object is null. How should I work around this? Here's what I've got:
public BaseController()
{
var resellerRepository = new ResellerRepository();
var reseller = resellerRepository.GetResellerByUsername(User.Identity.Name);
if (reseller.StoreSettingsID != null && reseller.StoreSetting.Theme != null)
{
var storeSettingsRepository = new StoreSettingsRepository();
var storeSettings = storeSettingsRepository.GetStoreSettings((int)reseller.StoreSettingsID);
ViewData["HeaderImage"] = storeSettings.Image1.FileName;
ViewData["FooterImage"] = storeSettings.Image.FileName;
ViewData["ThemeLocation"] = storeSettings.Theme.StylesheetLocation;
ViewData["StoreName"] = storeSettings.StoreName;
ViewData["Address1"] = storeSettings.Address1;
ViewData["Address2"] = storeSettings.Address2;
ViewData["City"] = storeSettings.City;
ViewData["PostalCode"] = storeSettings.PostalCode;
ViewData["ProvinceCode"] = storeSettings.Province.Abbreviation;
ViewData["Phone"] = storeSettings.Phone;
}
else
{
ViewData["ThemeLocation"] = "~/Content/jquery-ui-1.8.9.custom.css";
ViewData["StoreName"] = "";
ViewData["Address1"] = "";
}
}
The reseller must be logged on in order to view the store.
edit
So here is where I am at after following Darins advice - I upgraded to MVC3 and used a GlobalActionFilter, which is executing after EVERY action called. How do I prevent this - because it executes 4-5 times. Also - the viewdata is null every time. What am I doing wrong?
Here is my action filter (I didn't use Automapper from Darin's example because StoreSettings doesn't translate directly to StoreSettingsViewModel, and I wanted to see this working, first)
public class StoreSettingsActionFilter : ActionFilterAttribute
{
private readonly IResellerRepository _resellerRepository;
private readonly IStoreSettingsRepository _storeSettingsRepository;
public StoreSettingsActionFilter(
IResellerRepository resellerRepository,
IStoreSettingsRepository storeSettingsRepository
)
{
_resellerRepository = resellerRepository;
_storeSettingsRepository = storeSettingsRepository;
}
public StoreSettingsActionFilter()
: this(new ResellerRepository(), new StoreSettingsRepository())
{
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
var settingsViewModel = new StoreSettingsViewModel();
settingsViewModel.ThemeLocation = "~/Content/jquery-ui-1.8.9.custom.css";
var user = filterContext.HttpContext.User;
if (!user.Identity.IsAuthenticated || !user.IsInRole("Reseller"))
{
filterContext.Controller.ViewData["storeSettings"] = settingsViewModel;
return;
}
var session = filterContext.HttpContext.Session;
var reseller = session["reseller"] as Reseller;
if (reseller == null)
{
reseller = _resellerRepository.GetResellerByUsername(user.Identity.Name);
session["reseller"] = reseller;
}
if (reseller.StoreSettingsID != null && reseller.StoreSetting.Theme != null)
{
var storeSettings = session["storeSettings"] as StoreSettings;
if (storeSettings == null)
{
storeSettings = _storeSettingsRepository.GetStoreSettings((int)reseller.StoreSettingsID);
session["storeSettings"] = storeSettings;
}
// Using AutoMapper to convert between the model and the view model
//settingsViewModel = Mapper.Map<StoreSettings, StoreSettingsViewModel>(storeSettings);
settingsViewModel.ThemeLocation = storeSettings.Theme.StylesheetLocation;
settingsViewModel.Address1 = storeSettings.Address1;
settingsViewModel.Address2 = storeSettings.Address2;
settingsViewModel.City = storeSettings.City;
settingsViewModel.FooterImage = storeSettings.Image.FileName;
settingsViewModel.HeaderImage = storeSettings.Image1.FileName;
settingsViewModel.Phone = storeSettings.Phone;
settingsViewModel.PostalCode = storeSettings.PostalCode;
settingsViewModel.ProvinceCode = storeSettings.Province.Abbreviation;
settingsViewModel.StoreName = storeSettings.StoreName;
}
filterContext.Controller.ViewData["storeSettings"] = settingsViewModel;
}
}
Here is where i register the global action filter
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
//Register the global action filter
GlobalFilters.Filters.Add(new StoreSettingsActionFilter());
//RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
I would use an action filter that will inject the store settings on each request. The view model could look like this:
public class StoreSettingsViewModel
{
public string HeaderImage { get; set; }
public string FooterImage { get; set; }
public string ThemeLocation { get; set; }
public string StoreName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string ProvinceCode { get; set; }
public string Phone { get; set; }
}
And the action filter:
public class StoreSettingsActionFilter : ActionFilterAttribute
{
private readonly IResellerRepository _resellerRepository;
private readonly IStoreSettingsRepository _storeSettingsRepository;
public StoreSettingsActionFilter(
IResellerRepository resellerRepository,
IStoreSettingsRepository storeSettingsRepository
)
{
_resellerRepository = resellerRepository;
_storeSettingsRepository = storeSettingsRepository;
}
public StoreSettingsActionFilter()
: this(new ResellerRepository(), new StoreSettingsRepository())
{ }
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
var settingsViewModel = new StoreSettingsViewModel();
settingsViewModel.ThemeLocation = "~/Content/jquery-ui-1.8.9.custom.css";
var user = filterContext.HttpContext.User;
if (!user.Identity.IsAuthenticated || !user.IsInRole("Reseller"))
{
filterContext.Controller.ViewData["storeSettings"] = settingsViewModel;
return;
}
var session = filterContext.HttpContext;
var reseller = session["reseller"] as Reseller;
if (reseller == null)
{
reseller = _resellerRepository.GetResellerByUsername(user.Identity.Name);
session["reseller"] = reseller;
}
if (reseller.StoreSettingsID != null && reseller.StoreSetting.Theme != null)
{
var storeSettings = session["storeSettings"] as StoreSettings;
if (storeSettings == null)
{
storeSettings = _storeSettingsRepository.GetStoreSettings((int)reseller.StoreSettingsID);
session["storeSettings"] = storeSettings;
}
// Using AutoMapper to convert between the model and the view model
settingsViewModel = Mapper.Map<StoreSettings, StoreSettingsViewModel>(storeSettings);
}
filterContext.Controller.ViewData["storeSettings"] = settingsViewModel;
}
}
Now we need to apply this attribute on the base controller so that it is executed for each action:
[StoreSettings]
public abstract class BaseController : Controller
{
}
If you are using ASP.NET MVC 3 you could have a global action filter.
And finally inside the master page you would have access to the store settings:
<%
var storeSettings = (StoreSettingsViewModel)ViewData["storeSettings"];
%>
<title>
<asp:ContentPlaceHolder ID="TitleContent" runat="server" />
<%: storeSettings.StoreName ?? "My Store Name" %>
</title>
<%-- Css/JS --%>
...
I have the following code:
public ActionResult SomeAction()
{
return new JsonpResult
{
Data = new { Widget = "some partial html for the widget" }
};
}
I'd like to modify it so that I could have
public ActionResult SomeAction()
{
// will render HTML that I can pass to the JSONP result to return.
var partial = RenderPartial(viewModel);
return new JsonpResult
{
Data = new { Widget = partial }
};
}
is this possible? Could somebody explain how?
note, I edited the question before posting the solution.
I opted for an extension method like the following for an ASP.NET MVC 4 app. I think it's simpler than some of the suggestions I've seen:
public static class ViewExtensions
{
public static string RenderToString(this PartialViewResult partialView)
{
var httpContext = HttpContext.Current;
if (httpContext == null)
{
throw new NotSupportedException("An HTTP context is required to render the partial view to a string");
}
var controllerName = httpContext.Request.RequestContext.RouteData.Values["controller"].ToString();
var controller = (ControllerBase)ControllerBuilder.Current.GetControllerFactory().CreateController(httpContext.Request.RequestContext, controllerName);
var controllerContext = new ControllerContext(httpContext.Request.RequestContext, controller);
var view = ViewEngines.Engines.FindPartialView(controllerContext, partialView.ViewName).View;
var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
{
using (var tw = new HtmlTextWriter(sw))
{
view.Render(new ViewContext(controllerContext, view, partialView.ViewData, partialView.TempData, tw), tw);
}
}
return sb.ToString();
}
}
It allows me to do the following:
var html = PartialView("SomeView").RenderToString();
Also, this approach persists any Model, ViewBag and other view data for the view.
This is a slightly modified version of an answer that works:
public static string RenderPartialToString(string controlName, object viewData)
{
ViewPage viewPage = new ViewPage() { ViewContext = new ViewContext() };
viewPage.ViewData = new ViewDataDictionary(viewData);
viewPage.Controls.Add(viewPage.LoadControl(controlName));
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter tw = new HtmlTextWriter(sw))
{
viewPage.RenderControl(tw);
}
}
return sb.ToString();
}
Usage:
string ret = RenderPartialToString("~/Views/MyController/MyPartial.ascx", model);
DaveDev's answer worked well for me, however when the partial view calls another partial I get "Value cannot be null. Parameter name: view"
Searching around I have made a variant of the following that seems to work well.
public static string RenderPartialToString(string viewName, object model, ControllerContext ControllerContext)
{
if (string.IsNullOrEmpty(viewName))
viewName = ControllerContext.RouteData.GetRequiredString("action");
ViewDataDictionary ViewData = new ViewDataDictionary();
TempDataDictionary TempData = new TempDataDictionary();
ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
Usage:
String result = MVCHelpers.RenderPartialToString("PartialViewHere", Model, ControllerContext)
You can create extension that render view into string.
public static class RenderPartialToStringExtensions
{
/// <summary>
/// render PartialView and return string
/// </summary>
/// <param name="context"></param>
/// <param name="partialViewName"></param>
/// <param name="model"></param>
/// <returns></returns>
public static string RenderPartialToString(this ControllerContext context, string partialViewName, object model)
{
return RenderPartialToStringMethod(context, partialViewName, model);
}
/// <summary>
/// render PartialView and return string
/// </summary>
/// <param name="context"></param>
/// <param name="partialViewName"></param>
/// <param name="viewData"></param>
/// <param name="tempData"></param>
/// <returns></returns>
public static string RenderPartialToString(ControllerContext context, string partialViewName, ViewDataDictionary viewData, TempDataDictionary tempData)
{
return RenderPartialToStringMethod(context, partialViewName, viewData, tempData);
}
public static string RenderPartialToStringMethod(ControllerContext context, string partialViewName, ViewDataDictionary viewData, TempDataDictionary tempData)
{
ViewEngineResult result = ViewEngines.Engines.FindPartialView(context, partialViewName);
if (result.View != null)
{
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter output = new HtmlTextWriter(sw))
{
ViewContext viewContext = new ViewContext(context, result.View, viewData, tempData, output);
result.View.Render(viewContext, output);
}
}
return sb.ToString();
}
return String.Empty;
}
public static string RenderPartialToStringMethod(ControllerContext context, string partialViewName, object model)
{
ViewDataDictionary viewData = new ViewDataDictionary(model);
TempDataDictionary tempData = new TempDataDictionary();
return RenderPartialToStringMethod(context, partialViewName, viewData, tempData);
}
}
And then use it in action
[HttpPost]
public ActionResult GetTreeUnit(string id)
{
int _id = id.ExtractID();
string render = ControllerContext.RenderPartialToString("SomeView");
return Json(new { data = render });
}
Works perfect (Only view name required)
* for parameters you can use a model
* can call this from a view also
View side or Calling Side
BuyOnlineCartMaster ToInvoice1 = new BuyOnlineCartMaster(); // for passing parameters
ToInvoice1.CartID = 1;
string HtmlString = RenderPartialViewToString("PartialInvoiceCustomer", ToInvoice1);
Function Generating HTML
public class BuyOnlineCartMaster
{
public int CartID { get; set; }
}
public static string RenderPartialViewToString(string viewName, object model)
{
using (var sw = new StringWriter())
{
BuyOnlineController controller = new BuyOnlineController(); // instance of the required controller (you can pass this as a argument if needed)
// Create an MVC Controller Context
var wrapper = new HttpContextWrapper(System.Web.HttpContext.Current);
RouteData routeData = new RouteData();
routeData.Values.Add("controller", controller.GetType().Name
.ToLower()
.Replace("controller", ""));
controller.ControllerContext = new ControllerContext(wrapper, routeData, controller);
controller.ViewData.Model = model;
var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.ToString();
}
}
Partial View page
#{
var ModelContents = (Common.BuyOnlineCartMaster)ViewData.Model;
}
Your cart id : #(ModelContents.CartID)
Dave,
a variation on the same theme (mvc v1.0):
protected static string RenderPartialToString(Controller controller, string partialName, object model)
{
var vd = new ViewDataDictionary(controller.ViewData);
var vp = new ViewPage
{
ViewData = vd,
ViewContext = new ViewContext(),
Url = new UrlHelper(controller.ControllerContext.RequestContext)
};
ViewEngineResult result = ViewEngines
.Engines
.FindPartialView(controller.ControllerContext, partialName);
if (result.View == null)
{
throw new InvalidOperationException(
string.Format("The partial view '{0}' could not be found", partialName));
}
var partialPath = ((WebFormView)result.View).ViewPath;
vp.ViewData.Model = model;
Control control = vp.LoadControl(partialPath);
vp.Controls.Add(control);
var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
{
using (var tw = new HtmlTextWriter(sw))
{
vp.RenderControl(tw);
}
}
return sb.ToString();
}
usage within controller:
public string GetLocationHighlites()
{
IBlockData model = WebPagesMapper.GetLocationHighlites();
// **this** being the controoler instance
// LocationPartial.ascx can be defined in shared or in view folder
return RenderPartialToString(**this**,"LocationPartial", model);
}
You can do that out of the box with:
var partial = new HtmlString(Html.Partial("_myPartial", Model).ToString());
public virtual string RenderPartialViewToString(string viewName, object viewmodel)
{
if (string.IsNullOrEmpty(viewName))
{
viewName = this.ControllerContext.RouteData.GetRequiredString("action");
}
ViewData.Model = viewmodel;
using (var sw = new StringWriter())
{
ViewEngineResult viewResult = System.Web.Mvc.ViewEngines.Engines.FindPartialView(this.ControllerContext, viewName);
var viewContext = new ViewContext(this.ControllerContext, viewResult.View, this.ViewData, this.TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
return sw.GetStringBuilder().ToString();
}
}
Old post, but I think you are making this too difficult.
Why not return PartialView("<my partial>", model); will return the string you desire.
Just make the Get/Post method an IActionResult as JsonResult, PartialResultand other inherit from ActionResult you can send back anything including a JsonResult
[HttpPost]
public IActionResult CheckForContent(string id, string type){
try{
if(type = "something"){
return Json(new {
success = true,
myProp = "this prop"
});
} else {
MyViewModel model = new MyViewModel();
model.Content = _service.GetContent(new GetContentRequest(){ id = id }
return PartialView("<my partial>", model);
} catch(Exception ex){
_logger.LogError(ex.Message);
return Json(new { success = false }
}
}
Ajax:
$.ajax({
url: '/CheckForContent',
type: 'POST',
cache: false,
contentType: false,
processData: false,
success: function (result) {
if(!result && !result.success){
console.log("Failed to load");
} else if(result && result.success){
$('#MyProp').val(result.myProp);
} else {
$('#MyContainer').html(result);
}
},
});