Nopcommerce widget plugin dey - nopcommerce

I am new to nopcommerce plugin development but am learning very fast.
I am developing a widget plugin to display vendor information box in product details page, but am having some issues, the product page stopped displaying and throwing errors.
Below and in this question will be my codes and picture of the product page error.
Contoller:
[ChildActionOnly]
public ActionResult PublicInfo(string widgetZone, Vendor vm, object additionalData)
{
ActionResult actionResult;
EmptyResult emptyResult = new EmptyResult();
var activeStoreScopeConfiguration = this.GetActiveStoreScopeConfiguration(this._storeService, this._workContext);
VendorDetailsSettings VendorDetailsSetting = _settingService.LoadSetting<VendorDetailsSettings>(activeStoreScopeConfiguration);
if (VendorDetailsSetting != null)
{
int QproductId = Convert.ToInt32(System.Web.HttpContext.Current.Request.RequestContext.RouteData.Values["productId"]);
var productId = Convert.ToInt32(additionalData);
if (productId != 0)
{
Product productById = _productService.GetProductById(productId);
if (productById == null ? false : productById.VendorId != 0)
{
Vendor vendorById = _vendorService.GetVendorById(productById.VendorId);
if (vendorById == null || vendorById.Deleted ? false : vendorById.Active)
{
var Model = new PublicInfoModel();
//Model.Id = vendorById.Id;
ParameterExpression parameterExpression = Expression.Parameter(typeof(Vendor), "x");
Model.Name = LocalizationExtensions.GetLocalized<Vendor>(vendorById, Expression.Lambda<Func<Vendor, string>>(Expression.Property(parameterExpression, (MethodInfo)MethodBase.GetMethodFromHandle(typeof(Vendor).GetMethod("Name").MethodHandle)), new ParameterExpression[] { parameterExpression }));
parameterExpression = Expression.Parameter(typeof(Vendor), "x");
Model.Description = LocalizationExtensions.GetLocalized<Vendor>(vendorById, Expression.Lambda<Func<Vendor, string>>(Expression.Property(parameterExpression, (MethodInfo)MethodBase.GetMethodFromHandle(typeof(Vendor).GetMethod("Description").MethodHandle)), new ParameterExpression[] { parameterExpression }));
Model.SeName = SeoExtensions.GetSeName<Vendor>(vendorById);
PublicInfoModel model = Model;
if (VendorDetailsSetting.ShowVendorEmail || VendorDetailsSetting.ShowVendorPhoneNumer || VendorDetailsSetting.ShowVendorVendorRating)
{
model.Email = vendorById.Email;
}
return View("~/Plugins/Widgets.VendorDetails/Views/PublicInfo.cshtml", model);
}
else
{
actionResult = emptyResult;
}
}
else
{
actionResult = emptyResult;
}
}
else
{
actionResult = emptyResult;
}
}
else
{
actionResult = emptyResult;
}
return actionResult;
}
Plugin.cs:
public IList<string> GetWidgetZones()
{
return string.IsNullOrEmpty(_vendorDetailsSettings.WidgetZone) ? new List<string>() : new List<string> { _vendorDetailsSettings.WidgetZone };
}
public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues)
{
actionName = "Configure";
controllerName = "WidgetsVendorDetails";
routeValues = new RouteValueDictionary { { "Namespaces", "Nop.Plugin.Widgets.VendorDetails.Controllers" }, { "area", null } };
}
/// <summary>
/// Gets a route for displaying widget
/// </summary>
/// <param name="widgetZone">Widget zone where it's displayed</param>
/// <param name="actionName">Action name</param>
/// <param name="controllerName">Controller name</param>
/// <param name="routeValues">Route values</param>
public void GetDisplayWidgetRoute(string widgetZone, out string actionName, out string controllerName, out RouteValueDictionary routeValues)
{
actionName = "PublicInfo";
controllerName = "WidgetsVendoDetails";
routeValues = new RouteValueDictionary
{
{"Namespaces", "Nop.Plugin.Widgets.VendorDetails.Controllers"},
{"area", null},
{"widgetZone", widgetZone}
};
}
public override void Install()
{
var settings = new VendorDetailsSettings
{
WidgetZone = "productbox_add_info"
};
_settingService.SaveSetting(settings);
base.Install();
}
public override void Uninstall()
{
_settingService.DeleteSetting<VendorDetailsSettings>();
base.Uninstall();
}
}

The error was the product page didn't implement IControllers... But I have resolved it.. By Returning base. View() instead or just views...
Thanks.

Related

How to populate ViewDataDictionary inside an ExceptionFilter

How to retrieve the ViewModel from within an exception filter?
I have an ExceptionFilter, which I am using for a global error handler in an asp .net core 3.1 MVC application. I am trying to get the exception filter to redirect back to the View when there is an error and show validation errors, ie the equivalent of saying:
return View(viewModel)
in the controller
I can redirect to the View, but am a little stuck on how to populate the Model in the ViewResult
ExceptionFilter code
public void OnException(ExceptionContext context)
{
string controller = context.RouteData.Values["controller"].ToString();
string action = context.RouteData.Values["action"].ToString();
if (context.Exception is WebServiceException && context.Exception.IsUnauthorized())
{
context.Result = new RedirectToActionResult("fetchtoken", "Home", new { path = $"/{controller}/{action}" });
}
//other type of exception, return the view displaying errors
else
{
context.ModelState.Clear();
context.ModelState.AddModelError(action, $"error in {action}");
m_Logger.LogError(context.Exception, $"error in {action}");
context.ExceptionHandled = true;
context.ModelState
context.Result = new ViewResult{
ViewName = action,
ViewData = // ??????????????
};
}
}
In the controller:
[Authorize]
[HttpPost]
public async Task<IActionResult> AuthoriseApiUser(AuthoriseApiViewModel viewModel)
{
await m_ApiUserService.AuthoriseUser(viewModel.TenantId, viewModel.UserId); //error thrown here
return View(viewModel);
}
Through obtaining the value of each key in the form data, the value is compared with the property of the model. Then, assign value to model. For example.
public void OnException(ExceptionContext context)
{
string controller = context.RouteData.Values["controller"].ToString();
string action = context.RouteData.Values["action"].ToString();
//start
var viewModel = new ViewModel();
var list = context.HttpContext.Request.Form.AsEnumerable();
foreach (var meta in list)
{
if (meta.Key == "addr")
{
viewModel.addr = meta.Value;
}
}
//end
if (context.Exception is WebServiceException && context.Exception.IsUnauthorized())
{
context.Result = new RedirectToActionResult("fetchtoken", "Home", new { path = $"/{controller}/{action}" });
}
//other type of exception, return the view displaying errors
else
{
//...
var modelMetadata = new EmptyModelMetadataProvider();
context.Result = new ViewResult
{
ViewName = action,
ViewData = ViewData = new ViewDataDictionary(modelMetadata, context.ModelState)
{
Model = viewModel
}
};
}
}
Model
public class ViewModel
{
public int id { get; set; }
[MinLength(2)]
public string addr { get; set; }
}

How to overload UserManager.AddToRoleAsync(string userId, string role)

I'm using Asp.net Identity Framework 2.1. I implement customized ApplicatoinUser, ApplicationRole, ApplicationUserRole, because I want to add support to multi-tenant, that is each user belongs to different companies, but I have 3 roles among all these companies, they are User, Admin and Approver.
My ApplicationUserRole derived from IdentityUserRole, and have one more property: CompanyId. This property will indicate the user's role in this particular company. My code for these customized classes attached in bottom.
My question is when I try to override ApplicationUserManager(Yes, it derived from UserManager too)'s AddToRoleAsync , IsInRoleAsync , I don't know how to deal with the new CompanyId, looks like the existing function doesn't receive these companyId(or tenantId).
Then when I'm trying to overload these functions with companyId included, I can't find the db context either in ApplicatoinUserManager nor its base class.
Am I on the right track of adding tenantId/companyId to the application Role?
I've referenced this answer: SO linkes, and this blog.ASP.NET Web Api and Identity 2.0 - Customizing Identity Models and Implementing Role-Based Authorization
My IdentityModels:
public class ApplicationUserLogin : IdentityUserLogin<string> { }
public class ApplicationUserClaim : IdentityUserClaim<string>
{
}
public class ApplicationUserRole : IdentityUserRole<string>
{
public string CompanyId { get; set; }
}
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser<string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>//, IAppUser
{
public ApplicationUser()
{
this.Id = Guid.NewGuid().ToString();
}
public virtual string CompanyId { get; set; }
public virtual List<CompanyEntity> Company { get; set; }
public DateTime CreatedOn { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(ApplicationUserManager manager, string authenticationType)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
// Add custom user claims here
return userIdentity;
}
}
// Must be expressed in terms of our custom UserRole:
public class ApplicationRole : IdentityRole<string, ApplicationUserRole>
{
public ApplicationRole() {}
public ApplicationRole(string name) : this()
{
this.Name = name;
}
// Add any custom Role properties/code here
public string Description { get; set; }
}
// Most likely won't need to customize these either, but they were needed because we implemented
// custom versions of all the other types:
public class ApplicationUserStore: UserStore<ApplicationUser, ApplicationRole, string,ApplicationUserLogin, ApplicationUserRole,ApplicationUserClaim>, IUserStore<ApplicationUser, string>, IDisposable
{
public ApplicationUserStore()
: this(new IdentityDbContext())
{
base.DisposeContext = true;
}
public ApplicationUserStore(DbContext context)
: base(context)
{
}
}
public class ApplicationRoleStore
: RoleStore<ApplicationRole, string, ApplicationUserRole>,
IQueryableRoleStore<ApplicationRole, string>,
IRoleStore<ApplicationRole, string>, IDisposable
{
public ApplicationRoleStore()
: base(new IdentityDbContext())
{
base.DisposeContext = true;
}
public ApplicationRoleStore(DbContext context)
: base(context)
{
}
}
My IdentityConfig:
public class ApplicationUserManager
: UserManager<ApplicationUser, string>
{
public ApplicationUserManager(IUserStore<ApplicationUser, string> store)
: base(store) { }
public static ApplicationUserManager Create(
IdentityFactoryOptions<ApplicationUserManager> options,
IOwinContext context)
{
var manager = new ApplicationUserManager(
new UserStore<ApplicationUser, ApplicationRole, string,
ApplicationUserLogin, ApplicationUserRole,
ApplicationUserClaim>(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = false
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
//RequireNonLetterOrDigit = true,
//RequireDigit = true,
//RequireLowercase = true,
//RequireUppercase = true,
};
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(
dataProtectionProvider.Create("ASP.NET Identity"));
}
// add sms and email service provider
manager.SmsService = new EMaySmsServiceProvider();
manager.EmailService = new ConcordyaEmailServiceProvider();
return manager;
}
public string GetCurrentCompanyId(string userName)
{
var user = this.FindByName(userName);
if (user == null)
return string.Empty;
var currentCompany = string.Empty;
if (user.Claims.Count > 0)
{
currentCompany = user.Claims.Where(c => c.ClaimType == ConcordyaPayee.Core.Common.ConcordyaClaimTypes.CurrentCompanyId).FirstOrDefault().ClaimValue;
}
else
{
currentCompany = user.CurrentCompanyId;
}
return currentCompany;
}
public override Task<IdentityResult> AddToRoleAsync(string userId, string role, string companyId)
{
return base.AddToRoleAsync(userId, role);
}
#region overrides for unit tests
public override Task<bool> CheckPasswordAsync(ApplicationUser user, string password)
{
return base.CheckPasswordAsync(user, password);
}
public override Task<ApplicationUser> FindByNameAsync(string userName)
{
return base.FindByNameAsync(userName);
}
#endregion
}
public class ApplicationRoleManager : RoleManager<ApplicationRole>
{
public ApplicationRoleManager(IRoleStore<ApplicationRole, string> roleStore)
: base(roleStore)
{
}
public static ApplicationRoleManager Create(
IdentityFactoryOptions<ApplicationRoleManager> options,
IOwinContext context)
{
return new ApplicationRoleManager(
new ApplicationRoleStore(context.Get<ApplicationDbContext>()));
}
}
First of all, I would like to say thanks for taking it this far. It gave me a great start for my multi-tenant roles solution. I'm not sure if I'm 100% right, but this works for me.
Firstly, you cannot override any of the "RoleAsync" methods, but you can overload them. Secondly, the UserStore has a property called "Context" which can be set to your DbContext.
I had to overload the "RoleAsyc" methods in both my UserStore and UserManager extended classes. Here is an example from each to get you going:
MyUserStore
public class MyUserStore : UserStore<MyUser, MyRole, String, IdentityUserLogin, MyUserRole, IdentityUserClaim> {
public MyUserStore(MyDbContext dbContext) : base(dbContext) { }
public Task AddToRoleAsync(MyUser user, MyCompany company, String roleName) {
MyRole role = null;
try
{
role = Context.Set<MyRole>().Where(mr => mr.Name == roleName).Single();
}
catch (Exception ex)
{
throw ex;
}
Context.Set<MyUserRole>().Add(new MyUserRole {
Company = company,
RoleId = role.Id,
UserId = user.Id
});
return Context.SaveChangesAsync();
}
}
MyUserManager
public class MyUserManager : UserManager<MyUser, String>
{
private MyUserStore _store = null;
public MyUserManager(MyUserStore store) : base(store)
{
_store = store;
}
public Task<IList<String>> GetRolesAsync(String userId, int companyId)
{
MyUser user = _store.Context.Set<MyUser>().Find(new object[] { userId });
MyCompany company = _store.Context.Set<MyCompany>().Find(new object[] { companyId });
if (null == user)
{
throw new Exception("User not found");
}
if (null == company)
{
throw new Exception("Company not found");
}
return _store.GetRolesAsync(user, company);
}
}
From here a couple scary things happen and I don't know a better way to manage them.
The User "IsInRole" method in the HttpContext will work but it will not be tenant-sensitive so you can no longer use it.
If you use the "Authorize" attribute, the same idea for "scary thing 1" applies, but here you can just extend it and make things happy for your system. Example below:
MyAuthorizeAttribute
public class MyAuthorizeAttribute : AuthorizeAttribute {
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (null == httpContext)
{
throw new ArgumentNullException("httpContext");
}
HttpSessionStateBase session = httpContext.Session;
IList<String> authorizedRoleNames = Roles.Split(',').Select(r => r.Trim()).ToList();
if (!httpContext.User.Identity.IsAuthenticated)
{
return false;
}
if (null == session["MyAuthorize.CachedUsername"])
{
session["MyAuthorize.CachedUsername"] = String.Empty;
}
if (null == session["MyAuthorize.CachedCompanyId"])
{
session["MyAuthorize.CachedCompanyId"] = -1;
}
if (null == session["MyAuthorize.CachedUserCompanyRoleNames"])
{
session["MyAuthorize.CachedUserCompanyRoleNames"] = new List<String>();
}
String cachedUsername = session["MyAuthorize.CachedUsername"].ToString();
int cachedCompanyId = (int)session["MyAuthorize.CachedCompanyId"];
IList<String> cachedUserAllRoleNames = (IList<String>)session["MyAuthorize.CachedUserAllRoleNames"];
IPrincipal currentUser = httpContext.User;
String currentUserName = currentUser.Identity.Name;
int currentCompanyId = (int)session["CurrentCompanyId"];//Get this your own way! I used the Session in the HttpContext.
using (MyDbContext db = MyDbContext.Create())
{
try
{
MyUser mUser = null;
ICollection<String> tmpRoleIds = new List<String>();
if (cachedUsername != currentUserName)
{
session["MyAuthorize.CachedUsername"] = cachedUsername = String.Empty;
//Reload everything
mUser = db.Users.Where(u => u.Username == currentUserName).Single();
session["MyAuthorize.CachedUsername"] = currentUserName;
session["MyAuthorize.CachedCompanyId"] = cachedCompanyId = -1; //Force Company Reload
cachedUserCompanyRoleNames.Clear();
}
if (cachedUserCompanyRoleNames.Count != db.Users.Where(u => u.Username == currentUserName).Single().Roles.Select(r => r.RoleId).ToList().Count)
{
cachedUserCompanyRoleNames.Clear();
if (0 < currentCompanyId)
{
if(null == mUser)
{
mUser = db.Users.Where(u => u.Username == cachedUsername).Single();
}
tmpRoleIds = mUser.Roles.Where(r => r.Company.Id == currentCompanyId).Select(r => r.RoleId).ToList();
session["MyAuthorize.CachedUserCompanyRoleNames"] = cachedUserCompanyRoleNames = db.Roles.Where(r => tmpRoleIds.Contains(r.Id)).Select(r => r.Name).ToList();
session["MyAuthorize.CachedCompanyId"] = cachedCompanyId = currentCompanyId;
}
}
if (cachedCompanyId != currentCompanyId)
{
cachedUserCompanyRoleNames.Clear();
//Reload company roles
if (0 < currentCompanyId)
{
if(null == mUser)
{
mUser = db.Users.Where(u => u.Username == cachedUsername).Single();
}
tmpRoleIds = mUser.Roles.Where(r => r.Company.Id == currentCompanyId).Select(r => r.RoleId).ToList();
session["MyAuthorize.CachedUserCompanyRoleNames"] = cachedUserCompanyRoleNames = db.Roles.Where(r => tmpRoleIds.Contains(r.Id)).Select(r => r.Name).ToList();
session["MyAuthorize.CachedCompanyId"] = cachedCompanyId = currentCompanyId;
}
}
}
catch (Exception ex)
{
return false;
}
}
if (0 >= authorizedRoleNames.Count)
{
return true;
}
else
{
return cachedUserCompanyRoleNames.Intersect(authorizedRoleNames).Any();
}
}
}
In closing, as I said, I'm not sure if this is the best way to do it, but it works for me. Now, throughout your system, make sure you used your overloaded methods when dealing with Roles. I am also thinking about caching the Roles in a MVC BaseController that I wrote so that I can get similar functionality to User.IsInRole in all of my MVC Views.

An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter

I am struck at this code, i get this error when whenever i tried to see a list of articles.
the error is The parameters dictionary contains a null entry for parameter articleId of non-nullable type System.Int32 for method System.Web.Mvc.ActionResult Detail(Int32) in Crossroads.Controllers.ArticleController. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters.
Here is the controller:
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Security;
using Crossroads.Models;
using System.Linq;
using System.Web;
using Crossroads.Models.Repositories;
namespace Crossroads.Controllers
{
[HandleError]
public class ArticleController : BaseController
{
private IArticleRepository _ArticleRepository;
public ArticleController()
: this(new ArticleRepository())
{ }
public ArticleController(IArticleRepository articleRepo)
{
_ArticleRepository = articleRepo;
}
public ActionResult Index(bool? archived)
{
if (archived != null && archived.Value.Equals(true))
{
return View("IndexArchived", _ArticleRepository.ListArchivedArticles());
}
else
{
return View(_ArticleRepository.ListArticles());
}
}
public ActionResult Detail(int Id)
{
var article = _ArticleRepository.GetArticle(Id);
if (article.Published)
{
return View("Details", article);
}
else
{
postStatus = new PostStatusViewModel()
{
Status = Status.Warning,
Message = "I'm sorry, article " + article.Title + " has been deleted. <br />Please update your bookmark."
};
return RedirectToAction("PostStatus", postStatus);
}
}
public ActionResult Details(int id, string articleTitle)
{
return RedirectToAction("Detail", id);
}
[Authorize(Roles = "Manager")]
public ActionResult New()
{
return View("New", new Articles());
}
[Authorize(Roles = "Manager")]
[ValidateInput(false)]
public ActionResult Edit(int id)
{
var viewModel = _ArticleRepository.GetArticle(id);
return View(_ArticleRepository.GetArticle(id));
}
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
[Authorize(Roles = "Manager")]
public ActionResult Create(Articles model)
{
try
{
model.CreatedBy = Membership.GetUser().UserName;
model.CreatedDate = DateTime.Now;
model.ModifiedBy = Membership.GetUser().UserName;
model.ModifiedDate = DateTime.Now;
_ArticleRepository.CreateArticle(model);
return RedirectToAction("Index");
}
catch
{
return View("Edit", model.Id);
}
}
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
[Authorize(Roles = "Manager")]
public ActionResult Update(Articles model)
{
try
{
model.ModifiedBy = Membership.GetUser().UserName;
model.ModifiedDate = DateTime.Now;
_ArticleRepository.EditArticle(model);
return RedirectToAction("Details", new { id = model.Id });
}
catch (Exception ex)
{
_DBLogging.LoggError(ex);
postStatus = new PostStatusViewModel()
{
Status = Status.Error,
Message = ""
};
return View("Edit");
}
}
public JsonResult ManageArchiveArticle(int articleId, bool isArchived)
{
var result = new Dictionary<string, string>();
try
{
var article = _ArticleRepository.GetArticle(articleId);
article.IsArchived = isArchived ? false : true;
article.ModifiedBy = Membership.GetUser().UserName;
article.ModifiedDate = DateTime.Now;
if (article.IsArchived) article.ArchiveDate = DateTime.Now;
article = _ArticleRepository.EditArticle(article);
result.Add("isArchived", article.IsArchived.ToString().ToLower());
}
catch (Exception ex)
{
_DBLogging.LoggError(ex);
}
return Json(result);
}
public JsonResult ManagePublishArticle(int articleId, bool isPublished)
{
var result = new Dictionary<string, string>();
try
{
var article = _ArticleRepository.GetArticle(articleId);
article.Published = isPublished ? false : true;
article.ModifiedBy = Membership.GetUser().UserName;
article.ModifiedDate = DateTime.Now;
article = _ArticleRepository.EditArticle(article);
result.Add("isPublished", article.Published.ToString().ToLower());
}
catch (Exception ex)
{
_DBLogging.LoggError(ex);
}
return Json(result);
}
}
}
here is my index
<ul class="menu">
<li><%= Html.ActionLink("Article List", "Index", "Article")%></li>
<li><%= Html.ActionLink("New Article", "New", "Article")%></li>
</ul>

How to generate documentation for Asp.Net MVC?

With .net 4.0/Preview kit 2, we can generate help pages for WCF REST.
Is there anyway we can do the same for MVC ?
www.somewebsite.com/search/help
I can create help pages (views) and expose them.
I can generate XSD schema and spit out as xml.
Any other guidance/suggestions ?
I want to generate something similar to this.
UriTemplate http://somewebsite.com/Search/
Method PUT
Response Format Xml
Response Schema http://somewebsite.com/help/response/schema
Response Example http://somewebsite.com/help/response/example
Update:
I was able to create documentation by using below code.
Route : somewebsite.com/Media/
HelpRoute : somewebsite.com/Media/Help (Append help to the parent route)
Add routes accordingly. See below example.
routes.MapRoute("Search",
"Search/Quick/",
new { controller = "Search", action = "Search" },
new { httpMethod = new HttpMethodConstraint("PUT") }
);
routes.MapRoute("SearchHelp",
"Search/Quick/Help",
new { controller = "Search", action = "Search" },
new { helpConstraint = new HelpConstraint { Help = true, SampleType = typeof(Search) } }
);
public class HelpResult : ViewResult
{
private string HelpPage { get; set; }
private string folderName = "HelpFiles";
private HttpServerUtilityBase server { get; set; }
private Type sampleType { get; set; }
private string schemaName { get; set; }
const string htmlFormat =
#"
<html>
<head>
<title>Help</title>
</head>
<body>
<li>URL - {0}</li>
<li>Verb - {1}</li>
{2}
</body>
</html>
";
public override void ExecuteResult(ControllerContext context)
{
server = context.HttpContext.Server;
StringBuilder parentUrl = new StringBuilder();
var data = context.RouteData.Route.GetRouteData(context.HttpContext);
//Getting requested route url.
string url = ((Route)(data.Route)).Url;
//Getting parent route from requested route url by removing "Help" from the route.
string newUrl = url.Substring(0, url.Length - 4);
parentUrl.Append("/" + newUrl);
sampleType = data.GetSampleType();
var validVerbs = GetValidVerbs(MakeAppRelative(parentUrl.ToString()));
CreateSchema(sampleType, true);
HelpPage = string.Format(htmlFormat, newUrl, validVerbs, CreateInputSampleText());
context.HttpContext.Response.Output.Write(HelpPage);
}
private string CreateInputSampleText()
{
if (sampleType != null && !string.IsNullOrEmpty(sampleType.Name))
{
string sampleText =
#"<li>Input Sample Xml - <a href='\HelpFiles\{0}.xml'>Click Here</a></li>
<li>Input Sample Json - <a href='\HelpFiles\{0}.txt'>Click Here</a></li>
<li>Input Schema - <a href='\HelpFiles\{1}'>Click Here</a></li>";
sampleText = string.Format(sampleText, sampleType.Name, schemaName);
return sampleText;
}
return string.Empty;
}
private static string MakeAppRelative(string url)
{
if (!url.StartsWith("~"))
{
if (!url.StartsWith("/"))
url = "~/" + url;
else
url = "~" + url;
}
return url;
}
private static string GetValidVerbs(string Url)
{
StringBuilder validVerbs = new StringBuilder();
var httpMethodOptions = new[] { "GET", "POST", "PUT", "DELETE", "HEAD" };
foreach (var httpMethodOption in httpMethodOptions)
{
var fakeContext = new FakeHttpContext(MakeAppRelative(Url), httpMethodOption);
foreach (Route route in RouteTable.Routes)
{
var rd = route.GetRouteData(fakeContext);
if (rd != null)
{
bool errorControllerApplied = route.IsErrorController();
if (!errorControllerApplied)
{
validVerbs.Append(httpMethodOption);
}
}
}
}
return validVerbs.ToString();
}
private void CreateFile(Type type, Stream stream)
{
using (Stream inputStream = stream)
{
schemaName = sampleType + "Schema.xml";
string folder = Path.GetFullPath(Path.Combine(server.MapPath("~"), folderName));
string file = Path.Combine(folder, schemaName);
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
using (FileStream fileStream = new FileStream(file, FileMode.Create))
{
byte[] fileContent = new byte[inputStream.Length];
inputStream.Read(fileContent, 0, fileContent.Length);
fileStream.Write(fileContent, 0, fileContent.Length);
fileStream.Flush();
}
}
}
private void CreateSchema(Type type, bool isXmlSerializerType)
{
System.Collections.IEnumerable schemas;
if (isXmlSerializerType)
{
XmlReflectionImporter importer = new XmlReflectionImporter();
XmlTypeMapping typeMapping = importer.ImportTypeMapping(type);
XmlSchemas s = new XmlSchemas();
XmlSchemaExporter exporter = new XmlSchemaExporter(s);
exporter.ExportTypeMapping(typeMapping);
schemas = s.GetSchemas(null);
}
else
{
XsdDataContractExporter exporter = new XsdDataContractExporter();
exporter.Export(type);
schemas = exporter.Schemas.Schemas();
}
using (MemoryStream stream = new MemoryStream())
{
XmlWriterSettings xws = new XmlWriterSettings() { Indent = true };
using (XmlWriter w = XmlWriter.Create(stream, xws))
{
w.WriteStartElement("Schemas");
foreach (XmlSchema schema in schemas)
{
if (schema.TargetNamespace != "http://www.w3.org/2001/XMLSchema")
{
schema.Write(w);
}
}
}
stream.Seek(0, SeekOrigin.Begin);
CreateFile(type, stream);
}
}
public static class RouteDataExtensions
{
public static bool IsHelpConstraintApplied(this RouteData data)
{
if (data != null && data.Route != null)
{
var constraints = ((Route) (data.Route)).Constraints;
var helpConstraint = (from c in constraints.Values
where c.GetType().Equals(typeof (HelpConstraint))
select c).FirstOrDefault();
if (helpConstraint != null)
{
return true;
}
}
return false;
}
public static Type GetSampleType(this RouteData data)
{
if (data != null && data.Route != null)
{
var constraints = ((Route)(data.Route)).Constraints;
var helpConstraint = (from c in constraints.Values
where c.GetType().Equals(typeof(HelpConstraint))
select c).FirstOrDefault();
if (helpConstraint != null)
{
return ((HelpConstraint) helpConstraint).SampleType;
}
}
return null;
}
public static string GetMethodType(this RouteData data)
{
if (data != null && data.Route != null)
{
var constraints = ((Route) (data.Route)).Constraints;
var httpMethodConstraint = (from c in constraints.Values
where c.GetType().Equals(typeof (HttpMethodConstraint))
select c).FirstOrDefault();
if (httpMethodConstraint != null)
{
return ((HttpMethodConstraint) httpMethodConstraint).AllowedMethods.Single();
}
}
return null;
}
public static bool IsErrorController(this Route data)
{
if (data != null)
{
var defaults = ((Route)(data)).Defaults;
var controllerName = (from c in defaults.Values
where c.ToString().Contains("Error")
select c).FirstOrDefault();
if (controllerName != null)
{
return true;
}
}
return false;
}
public static RouteData GetRouteDataByUrl(this string url)
{
string httpMethod = "PUT";
var fakeContext = new FakeHttpContext(MakeAppRelative(url), httpMethod);
return RouteTable.Routes.GetRouteData(fakeContext);
}
private static string MakeAppRelative(string url)
{
if (!url.StartsWith("~"))
{
if (!url.StartsWith("/"))
url = "~/" + url;
else
url = "~" + url;
}
return url;
}
}
public class HelpConstraint : IRouteConstraint
{
public bool Help { get; set; }
public Type SampleType { get; set; }
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if(Help)
{
return true;
}
return false;
}
}
References
http://stephenwalther.com/blog/archive/2008/08/03/asp-net-mvc-tip-29-build-a-controller-to-debug-your-custom-routes.aspx
http://aspnet.codeplex.com/releases/view/24644
This code is not bug free. Please post improvements if you have any. Use it at your own risk.
Not exactly sure what you are looking for, but you can check out GhostDoc:
http://submain.com/products/ghostdoc.aspx
I use this to generate XML documentation in MVC.
Most probably you have solved your issue by now. Anyway, I think you need IApiExplorer. Have a look at this blog.

Is there a URL builder that supports request parameter concatenation as well?

I want to achieve something like the following:
UrlBuilder ub = new UrlBuilder("http://www.google.com/search");
ub.Parameters.Add("q","request");
ub.Parameters.Add("sourceid","ie8");
string uri = ub.ToString(); //http://www.google.com/search?q=request&sourceid=ie8
Is there anything in .NET, or I will have to create my own?
Nothing exists that I know of. Here's something simple which does what you want. Usage would be:
UrlBuilder ub = new UrlBuilder("www.google.com/search")
.AddQuery("q", "request")
.AddQuery("sourceid", "ie8");
string url=ub.ToString();
==
Code is:
public class UrlBuilder
{
private string _authority;
private string _host;
private int? _port;
private Dictionary<string, object> _query = new Dictionary<string, object>();
public UrlBuilder(string host)
: this("http", host, null)
{
}
public UrlBuilder(string authority, string host)
: this(authority, host, null)
{
}
public UrlBuilder(string authority, string host, int? port)
{
this._authority = authority;
this._host = host;
this._port = port;
}
public UrlBuilder AddQuery(string key, object value)
{
this._query.Add(key, value);
return this;
}
public override string ToString()
{
string url = _authority + "://" + _host;
if (_port.HasValue)
{
url += ":" + _port.ToString();
}
return AppendQuery(url);
}
private string AppendQuery(string url)
{
if (_query.Count == 0)
{
return url;
}
url += "?";
bool isNotFirst = false;
foreach (var key in this._query.Keys)
{
if (isNotFirst)
{
url += "&";
}
url += HttpUtility.UrlEncode(key) + "=" + HttpUtility.UrlEncode(this._query[key].ToString());
isNotFirst = true;
}
return url;
}
}
}
Does the UriBuilder class help?
It doesn't have any method to add querystring parameters. Look at Query property to set values.
EDIT: See UriTemplate class.
I developed my own, that's more suitable for my needs, thanks for your code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Collections.Specialized;
namespace Utils
{
public class ParameterCollectionBuilder : NameValueCollection
{
#region NameValueCollection Implementation
public ParameterCollectionBuilder() : base() { }
public ParameterCollectionBuilder(string uri)
{
Init(uri);
}
public ParameterCollectionBuilder(Uri uri) : this(uri.OriginalString) { }
public ParameterCollectionBuilder(NameValueCollection baseCollection)
{
foreach (string key in baseCollection.Keys) this[key] = baseCollection[key];
Init(ToString());
}
/// <summary>
///
/// </summary>
/// <param name="baseCollection"></param>
/// <param name="uri"></param>
/// <remarks>Note: any existing params in the uri will override the params in the collection.</remarks>
public ParameterCollectionBuilder(NameValueCollection baseCollection, string uri)
{
foreach (string key in baseCollection.Keys) this[key] = baseCollection[key];
Init(uri);
}
/// <summary>
///
/// </summary>
/// <param name="baseCollection"></param>
/// <param name="uri"></param>
/// <remarks>Note: any existing params in the uri will override the params in the collection.</remarks>
public ParameterCollectionBuilder(NameValueCollection baseCollection, Uri uri) : this(baseCollection, uri.OriginalString) { }
public override string ToString()
{
return Prefix + Query + Suffix;
}
/// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <remarks>Overides existing values.</remarks>
public new void Add(string name, object value)
{
Set(name, GetString(value));
}
/// <summary>
/// Add an array of key-value pairs separated by colon char ':'.
/// </summary>
/// <param name="names">Invalid items will be ignored.</param>
public void AddRange(string[] names)
{
rangeFlag = true;
for (int i = 0; i < names.Length; i++)
{
string item = names[i];
item = item.Replace("?", "");
item = item.Replace("&", "");
item = item.Replace("=", ":");
string[] pair = item.Split(':');
if (pair.Length == 2) Set(pair[0], pair[1]);
}
InitUri(FullString);
rangeFlag = false;
}
public void AppendQueryString(string query)
{
Add(BuildCollection(query));
}
public void RemoveRange(string[] keys)
{
rangeFlag = true;
foreach (string key in keys)
{
Remove(key);
}
InitUri(FullString);
rangeFlag = false;
}
bool rangeFlag = false;
public new void Set(string name, object value)
{
base.Set(name, GetString(value));
if (!rangeFlag && Uri != null) InitUri(FullString);
}
public override void Remove(string name)
{
base.Remove(name);
if (!rangeFlag && Uri != null) InitUri(FullString);
}
public override void Clear()
{
base.Clear();
if (Uri != null) InitUri(FullString);
}
#endregion NameValueCollection Implementation
static string ParseQuery(string uri)
{
string query = "";
if (!uri.Contains('=')) return query;
int
start = 0,
end = uri.Length;
if (uri.Contains('?')) start = uri.IndexOf('?');
if (uri.Contains(':')) end = uri.LastIndexOf(':');
query = uri.Substring(start, (start < end ? end : uri.Length) - start);
return query;
}
void Init(string uri)
{
if (Uri == null)
{
InitUri(uri);
}
OriginalQuery = ParseQuery(uri);
int qIndex = string.IsNullOrEmpty(OriginalQuery) ? uri.Length : uri.IndexOf(OriginalQuery);
Prefix = uri.Substring(0, qIndex);
Suffix = uri.Substring(qIndex + OriginalQuery.Length);
Merge(OriginalQuery);
}
void Merge(string query)
{
NameValueCollection col = BuildCollection(query);
foreach (string key in col.Keys)
{
string value = col[key];
if (!string.IsNullOrEmpty(value)) this[key] = value;
}
}
void InitUri(string uri)
{
try
{
Uri = new Uri(uri);
}
catch { }
}
static string GetString(object value)
{
return value is string ? value as string : value.ToString();
}
static NameValueCollection BuildCollection(string query)
{
NameValueCollection collection = new NameValueCollection();
if (string.IsNullOrEmpty(query) || !query.Contains('=')) return new NameValueCollection();
//Prepare string
query = query.ToLower();
if (!query.StartsWith("?"))
{
if (query.Contains('?')) query = query.Substring(query.IndexOf('?'));
}
query = query.Replace("?", "");
foreach (string pair in query.Split('&'))
{
string[] separated = pair.Split('=');
if (separated.Length == 2) collection[separated[0]] = separated[1];
}
return collection;
}
static string BuildQuery(NameValueCollection parameters)
{
string query = "";
Char separator = '?';
bool first = true;
foreach (string key in parameters.Keys)
{
query += string.Format("{0}{1}={2}", separator, key, parameters[key]);
if (first)
{
first = false;
separator = '&';
}
}
return query;
}
#region Properties
public Uri Uri { get; private set; }
public string Prefix { get; private set; }
public string OriginalQuery { get; private set; }
public string Suffix { get; private set; }
public string OriginalString
{
get
{
return Prefix + OriginalQuery + Suffix;
}
}
public string Query
{
get
{
return BuildQuery(this);
}
}
public string FullString
{
get
{
return ToString();
}
}
#endregion Properties
}
}
I would recommend you take a look at this article on CodeProject.
The author has extended the System.UriBuilder class and added a QueryString property that behaves in much the same way as the HttpRequest.QueryString property.
Using this class your example would become:
UrlBuilder ub = new UrlBuilder("http://www.google.com/search");
ub.QueryString.Add("q", "request");
ub.QueryString.Add("sourceid", "ie8");
string uri = ub.ToString(); //http://www.google.com/search?q=request&sourceid=ie8
It doesn't have a fluent interface like Josh's solution but could be easily extended to include one.
With Flurl [disclosure: I'm the author], your example would look like this:
string uri = "http://www.google.com/search"
.SetQueryParams(new { q = "request", sourceid = "ie8" });
The basic URL builder is available via NuGet:
PM> Install-Package Flurl
There's also a new companion lib that extends Flurl with fluent, testable HTTP:
PM> Install-Package Flurl.Http

Resources