I am trying to test the outbound URL of an action method in an MVC area using the MvcContrib TestHelper's static OutBoundUrl class. Here is what I have:
OutBoundUrl.Of<ErrorsController>(action => action.NotFound())
.ShouldMapToUrl("/errors/404");
I stepped into the source and found that the UrlHelper was returning null for the outbound URL. Basically it creates a new UrlHelper and delegates the task to UrlHelper.Action(action, controller, routeValues).
When invoked, routeValues does not contain anything (the action method takes no args). Is this a bug? Or do I need to do something extra to make this work with areas? The following line comes just before the above line in the unit test, so my routes are registered (the UrlHelper.RouteCollection contains 100+ routes).
"~/errors/404".WithMethod(HttpVerbs.Get)
.ShouldMapTo<ErrorsController>(action => action.NotFound());
The route is not named, and I don't want to name it, so please don't suggest I use the OfRouteNamed method.
Update
I made an extension method that could work, except for one thing. The OutBoundController Of<TController> method has this code:
var methodCall = ((MethodCallExpression)action.Body);
var methodName = methodCall.Method.Name;
There is similar, but critically different code in the RouteTestingExtensions ShouldMapTo<TController> method:
var methodCall = (MethodCallExpression) action.Body;
// this line is not important
string expectedAction = methodCall.Method.ActionName();
The ActionName() extension method is kind of awesome if you use ActionNameAttributes:
/// <summary>
/// Will return the name of the action specified in the ActionNameAttribute
/// for a method if it has an ActionNameAttribute.
/// Will return the name of the method otherwise.
/// </summary>
/// <param name="method"></param>
/// <returns></returns>
public static string ActionName(this MethodInfo method)
{
if (method.IsDecoratedWith<ActionNameAttribute>())
return method.GetAttribute<ActionNameAttribute>().Name;
return method.Name;
}
Back to the question... is this by design, and if so, why? The fact that OutBoundUrl does not use the .ActionName() extension is the one thing keeping me from writing an extension method to generate outbound url's for area actions decorated with [ActionName].
Related
Recently I'm trying to understand how NopCommerce plugins work.
I'll try to make my question as clear as possible and making sense for you without having the solution opened on your computer.
In nopCommerce plugins everything start with having a class which must have the definition for IWidgetPlugin interface and being derived from BasePlugin.
In one of the predefined plugins there is a class called NivoSliderPlugin and this class overrides some of it's base class methods and has the definition for IWidgetPlugin's methods.
here is the code:
public class NivoSliderPlugin : BasePlugin, IWidgetPlugin
{
// Here there are some fields that are referencing to different interfaces and then
they're injected to the class throw it's constructor
// This method gets a configuration page URL
public override string GetConfigurationPageUrl()
{
return _webHelper.GetStoreLocation() + "Admin/WidgetsNivoSlider/Configure";
}
}
In the above code I only mentioned the part of the code that I have a question about.
_webHelper is a reference to an interface and accept the incoming parameter of type bool.
Regardless the second part of the return, I mean the URL path which is "Admin/WidgetsNivoSlider/Configure", I'm wondering how _webHelper.GetStoreLocation() works ?
As you know _webHelper.GetStoreLocation() has no definition !
I hope what I just asked makes sense.
If it's not clear please let me know to make it clearer.
I'll appreciate it.
Let's start with IWidgetPlugin. This interface is not required to implement for all plugins in NopCommerce. This is required when you are developing a Widget type plugin. Here Nivo Slider is a widget type plugin, that's why it implemented IWidgetPlugin interface. Here is the implementation for Nivo slider.
/// <summary>
/// Gets a value indicating whether to hide this plugin on the widget list page in the admin area
/// </summary>
public bool HideInWidgetList => false;
/// <summary>
/// Gets a name of a view component for displaying widget
/// </summary>
/// <param name="widgetZone">Name of the widget zone</param>
/// <returns>View component name</returns>
public string GetWidgetViewComponentName(string widgetZone)
{
return "WidgetsNivoSlider";
}
/// <summary>
/// Gets widget zones where this widget should be rendered
/// </summary>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the widget zones
/// </returns>
public Task<IList<string>> GetWidgetZonesAsync()
{
return Task.FromResult<IList<string>>(new List<string> { PublicWidgetZones.HomepageTop });
}
If you want to develop a payment plugin, then you have to implement IPaymentMethod (i.e. PayPal, Stripe, Cash on Delivery) and IShippingRateComputationMethod for shipping methods (i.e. UPS, USPS). Also there are many other types of plugins available in nopCommerce. See the list below.
BasePlugin is an abstract class, which is required to be inherited for all plugin class. It has a virtual method named GetConfigurationPageUrl() which returns null by default. But this virtual method can be overridden from each plugin class and can return admin side configuration url for that plugin. Here is the overridden method for Nivo slider plugin.
/// <summary>
/// Gets a configuration page URL
/// </summary>
public override string GetConfigurationPageUrl()
{
return _webHelper.GetStoreLocation() + "Admin/WidgetsNivoSlider/Configure";
}
Here _webHelper.GetStoreLocation() is a method which returns base url of the site. This method is implemented in Nop.Core.WebHelper class. The optional boolean parameter (useSsl) for this method is used whether to consider https:// or not for the site base url. If this method is overridden from a specific plugin, then a Configure button will be displayed in local plugin list page.
guys:
I use swagger to make api document. I use ASP.NET WebAPI2 to develop WebAPI.
And I met three questions:
First: How could I Add Comments for the WebAPI Controller? I try to add Comment on Controller
namespace IMCAPI.Controllers
{
/// <summary>
/// Value API
/// </summary>
[Authorize]
public class ValuesController : ApiController
{
// GET api/values
/// <summary>
/// Get all Values
/// </summary>
/// <returns></returns>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
, but not work.
Second:How could I search specific WebAPI in Swagger by brower Url? like this Browser Url Search
For example, if I want to search Value API, I input Value in the red section,I want the search result would just show Value API, I don't want other API appears(like Account). Or how to setting SwaggerConfig.cs that can have the search function?
Third:I want to know whether swagger could just read one xml file? I search the Internet, they demo like this
private static string GetXmlCommentsPath()
{
return String.Format("{0}/App_Data/IMCAPI.XML", AppDomain.CurrentDomain.BaseDirectory);
}
If I have multiple XML file, how could I integrate in the swagger?
that it. Hope somebody can help me. Thank you!
Your first question: The comments are appearing, you've put them on the Get method in your code, they're appearing on the Get method in your screenshot. If what you want is a description of the whole service, the Swagger documentation says to use the Description method.
Your second: You can't it shows all the methods on that API.
Third: The Swagger documentation says you call IncludeXmlComments multiple times with the path to each XML.
I am creating AJAX enabled reusable controls in MVC razor code. It uses a Controller and 1 or more Razor views to work. What is the best practice for isolating those code files from the rest of my project? Logically, it does not make sense to me to have the Controller and the Views mixed in the with main Controller and View files I use for the rest of the project.
And what if i wanted to reuse the control?
I would probably make an extension method off HtmlHelper, so you can use it by calling:
#Html.MyControl("blah", "blah")
from within your view. This is how my MarkdownHelper works (though it's not actually a control, it just formats some text). This is also how the built-in stuff tends to work (g. Html.TextBox, etc.):
/// <summary>
/// Helper class for transforming Markdown.
/// </summary>
public static partial class MarkdownHelper
{
/// <summary>
/// Transforms a string of Markdown into HTML.
/// </summary>
/// <param name="helper">HtmlHelper - Not used, but required to make this an extension method.</param>
/// <param name="text">The Markdown that should be transformed.</param>
/// <returns>The HTML representation of the supplied Markdown.</returns>
public static IHtmlString Markdown(this HtmlHelper helper, string text)
{
// Transform the supplied text (Markdown) into HTML.
var markdownTransformer = new Markdown();
string html = markdownTransformer.Transform(text);
// Wrap the html in an MvcHtmlString otherwise it'll be HtmlEncoded and displayed to the user as HTML :(
return new MvcHtmlString(html);
}
}
I have a bunch of action methods inside my controller each with its own model binded as input parameter es.:
[HttpGet]
public MyActionMethod(MyCustomModel data){
...
}
...
public class MyCustomModel{
public int total {get;set;}
public string description {get;set;}
}
Now if I try to call the method passing the right set of querystring parameters to compose MyCustomModel everything works as expected.
If I redirect to the action method from another actionmethod using:
RedirectToAction("MyActionMethod", new { total=10, description="test"});
It also works as expected.
The problem is that I would like to achive some kind of strongly typed redirect something like:
RedirectToAction(c => c.MyActionMethod, new MyCustomModel{total=10, description="test"});
Something similar can be achived using the extension method provided by MvcContrib but unfortunately for some reason the extension can't compose the correct set of parameters in the querystring ending up with an incorrect request.
Any ideas?
You could do
TempData["MyCustomModel"] = new MyCustomModel{total=10, description="test"});
and then
MyCustomModel model = TempData["MyCustomModel"] as MyCustomModel
in the action method that's getting redirected to.
I am castle Windsor and it works great for controller constructors in passing in the repository that is being used.
private IStoryRepository Repository;
public StoryController(IStoryRepository Repository)
{
this.Repository = Repository;
}
Now I have an Action that is in the admin area to display the main admin menu. I have used a custom authorisation attribute which will just check that the logged in user is an admin (just an isAdmin flag in the users table)
[AdminAuthorize]
public ActionResult Menu()
private IStoryRepository Repository;
/// <summary>
/// Initializes a new instance of the <see cref="AdminAuthorizeAttribute"/> class.
/// </summary>
public AdminAuthorizeAttribute(IStoryRepository Repository)
{
this.Repository = Repository;
}
/// <summary>
/// Checks if the user is authorised
/// </summary>
/// <param name="httpContext">The HTTP context.</param>
/// <returns>
/// <c>true</c> if authorized; otherwise, <c>false</c>.
/// </returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return this.Repository.UserIsAdmin(httpContext.User.Identity.Name);
}
How can I get Castle to pass the repository into attribute constructor like it does for a controller constructor?
You basically have two options. Wrap the filter in a proxy, a good example of this can be found here.
Or, within your custom filter you can do an explicit container call. For example using StructureMap (I have no used castle extensively)
ObjectFactory.GetInstance(IStoryRepository)
There may be a third way which is to extend the ActionInvoker to do the injection but I am not sure how this would be done.
The problem is that attributes are constructed by reflection rather than through calls that can be intercepted and replaced with calls that delegate to the container.
There are numerous approaches that can be used to create filters that can support DI, the simplest IMHO is to extend the action invoker and override GetFilters, providing an implementation that uses the attribute to determine the filter type and then resolving that type from the container. An implementation of this approach can be seen in MvcTurbine ( http://mvcturbine.codeplex.com/sourcecontrol/changeset/view/37298?projectName=mvcturbine#758440 ).
Why don't you get the IRepository object from a static factory method inside the Filter constructor? You just use the factory method in a way to allow DI to do its work.
DI will work on your "gateway" method instead of the standard "constructor parameter" approach.
I might be worth looking at FluentMVC project. It allows you to configure attributes at startup and because it uses windsor under the hood should allow for this to be injected pritty easily. For example
FluentMvcConfiguration.Configure = x => {
x.UsingControllerFactory(new WindsorControllerFactory()); x.WithFilter<HandleErrorAttribute>();
x.WithFilter<AuthorizeAttribute>(
Except
.For<AccountController>(ac => ac.LogOn())
.AndFor<AccountController>(ac => ac.LogOn(null, null, false, null))
.AndFor<HomeController>());
};
The code above will add the AuthorizeAttribute to all actions except Login and the home controller
Not sure what the current state of the project is but have used it a few times and works pretty well for me.